1. 개요

이 예제에서는  인증 코드 흐름을 위한 간단한 프런트 엔드를 구축하여 Spring Security OAuth 시리즈 를 계속합니다.

여기서 초점은 클라이언트 측이라는 점을 명심하십시오. Spring REST API + OAuth2 + AngularJS 작성 을 살펴보고   Authorization 및 Resource Server 모두에 대한 자세한 구성을 검토하십시오.

2. 인증 서버

프런트 엔드에 도달하기 전에 Authorization Server 구성에 클라이언트 세부 정보를 추가해야 합니다.

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
               .withClient("fooClientId")
               .secret(passwordEncoder().encode("secret"))
               .authorizedGrantTypes("authorization_code")
               .scopes("foo", "read", "write")
               .redirectUris("http://localhost:8089/")
...

이제 다음과 같은 간단한 세부 정보와 함께 인증 코드 부여 유형이 어떻게 활성화되었는지 확인하십시오.

  • 클라이언트 ID는  fooClientId 입니다.
  • 우리의 범위는  foo읽기  및  쓰기 입니다.
  • 리디렉션 URI는  http://localhost:8089/ 입니다(프론트 엔드 앱에 포트 8089 사용).

3. 프런트 엔드

이제 간단한 프런트 엔드 애플리케이션 구축을 시작하겠습니다.

여기에서 앱에 Angular 6을 사용할 것이므로  Spring Boot 애플리케이션에서 frontend-maven-plugin 플러그인을 사용해야 합니다.

<plugin>
    <groupId>com.github.eirslett</groupId>
    <artifactId>frontend-maven-plugin</artifactId>
    <version>1.6</version>

    <configuration>
        <nodeVersion>v8.11.3</nodeVersion>
        <npmVersion>6.1.0</npmVersion>
        <workingDirectory>src/main/resources</workingDirectory>
    </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>

당연히 상자에 Node.js 를 먼저 설치해야 합니다. Angular CLI를 사용하여 앱의 기반을 생성합니다.

ng 새 인증 코드

4. Angular 모듈

이제 Angular Module에 대해 자세히 살펴보겠습니다.

간단한 AppModule 은 다음과 같습니다 .

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule }   from '@angular/router';
import { AppComponent } from './app.component';
import { HomeComponent } from './home.component';
import { FooComponent } from './foo.component';

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    FooComponent    
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    RouterModule.forRoot([
     { path: '', component: HomeComponent, pathMatch: 'full' }], {onSameUrlNavigation: 'reload'})
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

모듈은 3개의 구성 요소와 1개의 서비스로 구성되며 다음 섹션에서 이에 대해 설명합니다.

4.1. 앱 구성요소

루트 구성 요소인 AppComponent 부터 시작하겠습니다 .

import {Component} from '@angular/core';
 
@Component({
    selector: 'app-root',
    template: `<nav class="navbar navbar-default">
  <div class="container-fluid">
    <div class="navbar-header">
      <a class="navbar-brand" href="/">Spring Security Oauth - Authorization Code</a>
    </div>
  </div>
</nav>
<router-outlet></router-outlet>`
})

export class AppComponent {}

4.2. 홈 구성 요소

다음은 메인 컴포넌트인 HomeComponent 입니다 .

import {Component} from '@angular/core';
import {AppService} from './app.service'
 
@Component({
    selector: 'home-header',
    providers: [AppService],
  template: `<div class="container" >
    <button *ngIf="!isLoggedIn" class="btn btn-primary" (click)="login()" type="submit">Login</button>
    <div *ngIf="isLoggedIn" class="content">
        <span>Welcome !!</span>
        <a class="btn btn-default pull-right"(click)="logout()" href="#">Logout</a>
        <br/>
        <foo-details></foo-details>
    </div>
</div>`
})
 
export class HomeComponent {
     public isLoggedIn = false;

    constructor(
        private _service:AppService){}
 
    ngOnInit(){
        this.isLoggedIn = this._service.checkCredentials();    
        let i = window.location.href.indexOf('code');
        if(!this.isLoggedIn && i != -1){
            this._service.retrieveToken(window.location.href.substring(i + 5));
        }
    }

    login() {
        window.location.href = 'http://localhost:8081/spring-security-oauth-server/oauth/authorize?response_type=code&client_id=' + this._service.clientId + '&redirect_uri='+ this._service.redirectUri;
    }
 
    logout() {
        this._service.logout();
    }
}

참고:

  • 사용자가 로그인하지 않은 경우 로그인 버튼만 나타납니다.
  • 로그인 버튼은 인증 URL로 사용자를 리디렉션합니다.
  • 인증 코드로 사용자가 다시 리디렉션되면 이 코드를 사용하여 액세스 토큰을 검색합니다.

4.3. 푸 컴포넌트

세 번째이자 마지막 구성 요소는 FooComponent 입니다 . 그러면 리소스 서버에서 가져온 Foo  리소스 가 표시됩니다 .

import { Component } from '@angular/core';
import {AppService, Foo} from './app.service'

@Component({
  selector: 'foo-details',
  providers: [AppService],  
  template: `<div class="container">
    <h1 class="col-sm-12">Foo Details</h1>
    <div class="col-sm-12">
        <label class="col-sm-3">ID</label> <span>{{foo.id}}</span>
    </div>
    <div class="col-sm-12">
        <label class="col-sm-3">Name</label> <span>{{foo.name}}</span>
    </div>
    <div class="col-sm-12">
        <button class="btn btn-primary" (click)="getFoo()" type="submit">New Foo</button>        
    </div>
</div>`
})

export class FooComponent {
    public foo = new Foo(1,'sample foo');
    private foosUrl = 'http://localhost:8082/spring-security-oauth-resource/foos/';  

    constructor(private _service:AppService) {}

    getFoo(){
        this._service.getResource(this.foosUrl+this.foo.id)
         .subscribe(
            data => this.foo = data,
            error =>  this.foo.name = 'Error');
    }
}

4.4. 앱 서비스

이제 AppService 를 살펴보겠습니다 .

import {Injectable} from '@angular/core';
import { Cookie } from 'ng2-cookies';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
 
export class Foo {
  constructor(
    public id: number,
    public name: string) { }
} 

@Injectable()
export class AppService {
   public clientId = 'fooClientId';
   public redirectUri = 'http://localhost:8089/';

  constructor(
    private _http: HttpClient){}

  retrieveToken(code){
    let params = new URLSearchParams();   
    params.append('grant_type','authorization_code');
    params.append('client_id', this.clientId);
    params.append('redirect_uri', this.redirectUri);
    params.append('code',code);

    let headers = new HttpHeaders({'Content-type': 'application/x-www-form-urlencoded; charset=utf-8', 'Authorization': 'Basic '+btoa(this.clientId+":secret")});
     this._http.post('http://localhost:8081/spring-security-oauth-server/oauth/token', params.toString(), { headers: headers })
    .subscribe(
      data => this.saveToken(data),
      err => alert('Invalid Credentials')
    ); 
  }

  saveToken(token){
    var expireDate = new Date().getTime() + (1000 * token.expires_in);
    Cookie.set("access_token", token.access_token, expireDate);
    console.log('Obtained Access token');
    window.location.href = 'http://localhost:8089';
  }

  getResource(resourceUrl) : Observable<any>{
    var headers = new HttpHeaders({'Content-type': 'application/x-www-form-urlencoded; charset=utf-8', 'Authorization': 'Bearer '+Cookie.get('access_token')});
    return this._http.get(resourceUrl,{ headers: headers })
                   .catch((error:any) => Observable.throw(error.json().error || 'Server error'));
  }

  checkCredentials(){
    return Cookie.check('access_token');
  } 

  logout() {
    Cookie.delete('access_token');
    window.location.reload();
  }
}

여기에서 구현에 대해 간단히 살펴보겠습니다.

  • checkCredentials() : 사용자가 로그인했는지 확인
  • retrieveToken() : 인증 코드를 사용하여 액세스 토큰을 얻습니다.
  • saveToken() : Access Token을 쿠키에 저장
  • getResource() : ID를 사용하여 Foo 세부 정보를 가져옵니다.
  • logout() : Access Token 쿠키 삭제

5. 애플리케이션 실행

애플리케이션을 실행하고 모든 것이 제대로 작동하는지 확인하려면 다음을 수행해야 합니다.

  • 먼저 포트 8081에서 Authorization Server를 실행합니다.
  • 그런 다음 포트 8082에서 리소스 서버를 실행합니다.
  • 마지막으로 프런트 엔드를 실행합니다.

먼저 앱을 빌드해야 합니다.

mvn clean install

그런 다음 디렉토리를 src/main/resources로 변경합니다.

cd src/main/resources

그런 다음 포트 8089에서 앱을 실행합니다.

npm start

6. 결론

Spring 및 Angular 6을 사용하여 인증 코드 흐름을 위한 간단한 프런트 엔드 클라이언트를 구축하는 방법을 배웠습니다.

그리고 항상 그렇듯이 전체 소스 코드는 GitHub에서 사용할 수 있습니다 .

Security footer banner