Coder Social home page Coder Social logo

asb-basic's Introduction

Angular - Spring Boot - Basic

It's a simple full stack project with Angular frontend and Java - Spring Boot backend. You can use it as a template for your own projects.

Development environment configuration

Install node from https://nodejs.org/.

$ node --version
v14.18.1

Upgrade npm and install Angular CLI

$ npm install -g npm
$ npm install -g @angular/cli

Check ng command

$ ng version
     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 12.2.11
Node: 14.18.1
Package Manager: npm 8.1.2
OS: win32 x64

Angular:
...

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.1202.11 (cli-only)
@angular-devkit/core         12.2.11 (cli-only)
@angular-devkit/schematics   12.2.11 (cli-only)
@schematics/angular          12.2.11 (cli-only)

I use also:

Generate standard projects

Create parent folder for both projects.

mkdir my_project
cd my_project

Create Angular project with two example components.

ng new frontend --routing
cd frontend
ng add @angular/material
ng generate @angular/material:navigation nav
ng generate @angular/material:dashboard home

You can run it.

ng serve --open

Create Spring Boot project with Spring Web and Spring Security dependencies. You can use https://start.spring.io/.

Save backend.zip in parent folder for both projects and unzip it.

unzip backend.zip

You can run it.

cd backend
mvn spring-boot:run

When you open http://localhost:8080/ you will see the login form. User name is "user" and password was generated and it was printed in console. After login you will see the "Whitelabel Error Page".

Using generated security password: 7e1f5805-99b4-4aba-af2e-db2e10deb8b9

Development environment

In production use there will be one server, but in development two separate servers are more convinient. As you can see before, there are two saparate servers working on ports 4200 (Angular) and 8080 (Spring Boot). In that case there is a problem with CORS (Cross-Origin Resource Sharing) and CSRF (Cross-site request forgery) protection. It is possible to configure Spring Boot Server to bypass these protections, but for me it is easier to use NGINX serwer in Docker container as a proxy.

There are configured three diffrent locations:

  • / - Angular code and resources
  • /api - resorces served by Spring Boot
  • /sso - authorization methods - in this case also served by Spring Boot

For development process you should run Angular serwer and Spring Boot serwer as previous and NGINX in docker.

In this and next steps you have to get proper files from the repository!

cd nginx-dev
docker build -t leliw/nginx-angular-dev .
docker run -p 80:80 -d --name nginx-angular-dev leliw/nginx-angular-dev

If Angular serwer still working, you will see Angular default page on http://localhost/.

Angular environment properties

Add these paths to environment properties (frontend/src/environments/environment.ts and environment.prod.ts).

export const environment = {
  production: false,
  apiUrl: '/api',
  ssoUrl: '/sso'  
};

Angular login form and authentication service

Frontend

All sources of Angular application are located in fornted/src/app and it is root for the rest of the files.

  • Generate app.service.ts
ng generate service app
  • Add content
interface Principal {
  name: string;
}

@Injectable({
  providedIn: 'root'
})
export class AppService {

  authenticated = false;

  constructor(private http: HttpClient) {
      this.http.get<Principal>(environment.ssoUrl + '/user').subscribe(response => {
          if (response != null && response['name']) {
              this.authenticated = true;
          } else {
              this.authenticated = false;
          }
          console.log(this.authenticated);
      });
  }

  authenticate(credentials: { username: string; password: string; }, callback: () => any) {
      const headers = new HttpHeaders(credentials ? {
          authorization: 'Basic ' + btoa(credentials.username + ':' + credentials.password)
      } : {});
      this.http.get<Principal>(environment.ssoUrl + '/user', { headers: headers }).subscribe(response => {
          if (response['name']) {
              this.authenticated = true;
          } else {
              this.authenticated = false;
          }
          console.log(this.authenticated);
          return callback && callback();
      });
  }

  logout() {
      this.http.post(environment.ssoUrl + '/logout', {}).subscribe();
      this.authenticated = false;
      console.log(this.authenticated);
  }

}
  • Add construtor to app.component.ts
constructor(public appService : AppService, public router : Router) {}
  • Change app.component.html
<app-login *ngIf="appService.authenticated === false || router.url === '/login'"></app-login>
<app-nav *ngIf="appService.authenticated === true"></app-nav>
<div *ngIf="appService.authenticated !== true && appService.authenticated !== false">Wait a moment ...</div>
  • Generate login component
ng generate component login
  • Copy login component (frontend/src/apps/login directory) and add it to app.module.ts as well as other required modules
...
import { LoginComponent } from './login/login.component';
import { LoginComponent } from './login/login.component';
import { MatFormFieldModule } from '@angular/material/form-field';
import { AppService } from './app.service';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatInputModule } from '@angular/material/input';


@NgModule({
  declarations: [
    AppComponent,
    NavComponent,
    HomeComponent,
    LoginComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    AppRoutingModule,
    LayoutModule,
    FormsModule,
    ReactiveFormsModule,
    HttpClientModule,
    MatMenuModule,
    MatFormFieldModule,
    MatInputModule
  ],
  providers: [AppService],
...

On http://localhost:4200/ or http://localhost/ you should see login component / login form (backend still not responding).

Backend

  • Add @RestController annotation to main class
  • Add authentication method user(Principal user)
  • Change logout URI (default is /logout)
  • All URI started with /api and /sso require authentication and the rest doesn't (Angular static sources)
  • Configure CSRF protection for Angular and disable it for /login and /logout
  • CORS default protections is left without changes
	@GetMapping("/sso/user")
	@ResponseBody
	public Principal user(Principal user) {
		return user;
	}
	
	@Configuration
	@EnableWebSecurity
	protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
		@Override
		protected void configure(HttpSecurity http) throws Exception {
			http.httpBasic()
			.and()
				.logout()
				.logoutUrl("/sso/logout")
			.and()
				.authorizeRequests().antMatchers("/sso", "/api").authenticated()
				.anyRequest().permitAll()
			.and()
				.csrf()
				.ignoringAntMatchers ("/login","/logout")
				.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
		}
	}

After restarting both servers (backend and frontend) you should see login form and be able to login.

Frontend logout, routing and simple navigation

  • Add routing path in app-routing.module.ts
const routes: Routes = [
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  { path: 'login', component: LoginComponent },
  { path: 'home', component: HomeComponent }  
];
  • Add logout() method in nav.component.ts
  constructor(private breakpointObserver: BreakpointObserver,
    public appService: AppService) {}

  logout() {
    this.appService.logout();
  }
  • Add logout icon (nav.component.html)
      <span>frontend</span>
      <span class="spacer"></span>
      <button mat-icon-button aria-label="Logout">
        <mat-icon aria-hidden="false" aria-label="Logout" (click)="logout()">logout</mat-icon>
      </button>
    </mat-toolbar>
    <router-outlet></router-outlet>
  </mat-sidenav-content>
</mat-sidenav-container>
  • Move logout icon to right by adding in nav.component.css
.spacer {
  flex: 1 1 auto;
}
  • Menu links doesn't work so good, but when they are routerLinks it looks better (nav.component.html)
    <mat-nav-list>
      <a mat-list-item routerLink="/home">Home</a>
      <a mat-list-item href="#" *ngIf="appService.authenticated">Link</a>
    </mat-nav-list>
  • There is also a problem with browser login form, so disable it (app.modules.ts)
@Injectable()
export class XhrInterceptor implements HttpInterceptor {

  constructor(private router: Router) {
    
  }
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const xhr = req.clone({
      headers: req.headers.set('X-Requested-With', 'XMLHttpRequest'),
      withCredentials: true
    });
    return next.handle(xhr).pipe(
      map((event: HttpEvent<any>) => event), // pass further respone
      catchError((error: HttpErrorResponse) => {
          // here will be catched error from response, just check if its 401
          if (error && error.status == 401)
            this.router.navigate(['login']);
          return throwError(error);
      }));
  }
}

...

  ],
  providers: [AppService, { provide: HTTP_INTERCEPTORS, useClass: XhrInterceptor, multi: true }],
  bootstrap: [AppComponent]
})
export class AppModule { }

All together in production environment

In production environment there is only one server. Angular resources are served by Java code as static files.

  • Bilding Angular source by Maven. Add pom.xml file in frontend folder with frontend-maven-plugin plugin
            <plugin>
                <groupId>com.github.eirslett</groupId>
                <artifactId>frontend-maven-plugin</artifactId>
                <version>1.7.6</version>
                <configuration>
                    <workingDirectory>./</workingDirectory>
                    <nodeVersion>v14.18.1</nodeVersion>
                    <npmVersion>8.1.2</npmVersion>
                </configuration>
                <executions>
                    <execution>
                        <id>install node and npm</id>
                        <goals>
                            <goal>install-node-and-npm</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>npm install</id>
                        <goals>
                            <goal>npm</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>npm run build</id>
                        <goals>
                            <goal>npm</goal>
                        </goals>
                        <configuration>
                            <arguments>run build</arguments>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
  • Add copying Angular files do static floder in backend/pom.xml
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-resources-plugin</artifactId>
				<version>2.4.2</version>
				<executions>
					<execution>
						<id>default-copy-resources</id>
						<phase>process-resources</phase>
						<goals>
							<goal>copy-resources</goal>
						</goals>
						<configuration>
							<overwrite>false</overwrite>
							<outputDirectory>target/classes/static</outputDirectory>
							<resources>
								<resource>
									<directory>../frontend/dist/frontend</directory>
								</resource>
							</resources>
						</configuration>
					</execution>
				</executions>
			</plugin>
  • Parent pom.xml file in main folder apoint both projects
  <packaging>pom</packaging>

  <modules>
    <module>frontend</module>
    <module>backend</module>
  </modules>

Then build and (stop docker first) run built jar.

mvn clean install
java -jar backend/target/backend-0.0.1-SNAPSHOT.jar

Application is available at http://localhost:8080/.

asb-basic's People

Contributors

leliw avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.