Spring Security OAuthを使用したフロントエンドアプリケーション - 認証コードフロー

1概要

このチュートリアルでは、Authorization Codeフロー用の簡単なフロントエンドを構築することで、リンクを続けます:/spring-security-oauth[Spring Security OAuthシリーズ]。

ここでの焦点はクライアントサイドです。リンクを見てください:/rest-api-spring-oauth2-angularjs[Spring REST API OAuth2 AngularJS]writeup - 承認サーバーとリソースサーバーの両方の詳細な設定を確認します。

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 __read そして write__です。

  • リダイレクトURIは http://localhost:8089/ です(私たちはportを使うつもりです)

私たちのフロントエンドのアプリのための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>

もちろん、最初にhttps://nodejs.org/en/[Node.js]をインストールする必要があります。 Angular CLIを使用してアプリのベースを生成します。

4角度付きモジュール

それでは、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. Fooコンポーネント

最後の3番目のコンポーネントは FooComponent です。これは、Resource Serverから取得した 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() :アクセストークンをクッキーに保存する

  • getResource() :そのIDを使ってFooの詳細を取得する

  • logout() :アクセストークンクッキーを削除する

5アプリケーションを実行します

アプリケーションを実行し、すべてが正しく機能していることを確認するには、次の手順を実行する必要があります。

  • まず、ポート8081で認証サーバーを実行します。

  • その後、ポート8082でリソースサーバーを実行します。

  • 最後に、フロントエンドを実行します

まずアプリを作成する必要があります。

mvn clean install

それから、ディレクトリをsrc/main/resourcesに変更します。

cd src/main/resources

その後、ポート8089で我々のアプリを実行します。

npm start

6. 結論

SpringとAngular 6を使用して、認証コードフロー用のシンプルなフロントエンドクライアントを構築する方法を学びました。

そして、いつものように、完全なソースコードはhttps://github.com/Baeldung/spring-security-oauth[over on GitHub]から入手可能です。