1概要

このチュートリアルでは、Spring Securityを使って

ログインページを作成します。

  • AngularJS

  • 角度2、4、5、6

ここで説明するサンプルアプリケーションは、基本的なHTTP認証で保護されたRESTサービスと通信するクライアントアプリケーションで構成されています。


2 Springのセキュリティ設定

まず最初に、Spring SecurityとBasic Authを使ってREST APIを設定しましょう。

設定方法は次のとおりです。

@Configuration
@EnableWebSecurity
public class BasicAuthConfiguration
  extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth)
      throws Exception {
        auth
          .inMemoryAuthentication()
          .withUser("user")
          .password("password")
          .roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http)
      throws Exception {
        http.csrf().disable()
          .authorizeRequests()
          .antMatchers("/login").permitAll()
          .anyRequest()
          .authenticated()
          .and()
          .httpBasic();
    }
}

それでは、エンドポイントを作成しましょう。 RESTサービスには、ログイン用とユーザーデータ取得用の2つがあります。

@RestController
@CrossOrigin
public class UserController {

    @RequestMapping("/login")
    public boolean login(@RequestBody User user) {
        return
          user.getUserName().equals("user") && user.getPassword().equals("password");
    }

    @RequestMapping("/user")
    public Principal user(HttpServletRequest request) {
        String authToken = request.getHeader("Authorization")
          .substring("Basic".length()).trim();
        return () ->  new String(Base64.getDecoder()
          .decode(authToken)).split(":")[0];
    }
}

同様に、認可のためにOAuth2サーバーを実装することに興味がある場合は、https://www.baeldung.com/sso-spring-security-oauth2[Spring Security OAuth2]についての他のチュートリアルもチェックできます。


3 Angular Clientをセットアップする

RESTサービスを作成したので、次に、異なるバージョンのAngularクライアントでログインページを設定しましょう。

ここで紹介する例では、依存関係管理に

npm

、アプリケーションの実行に

nodejs

を使用しています。

Angularは、すべての子コンポーネント(この例ではログインコンポーネントとホームコンポーネント)が共通の親DOMにインジェクトされる単一ページアーキテクチャを使用しています。

JavaScriptを使用するAngularJSとは異なり、Angularバージョン2以降はTypeScriptを主言語として使用します。したがって、アプリケーションはそれが正しく機能するために必要な特定のサポートファイルも必要とします。

Angularの機能強化により、必要なファイルはバージョンごとに異なります。

それぞれについて説明しましょう。


  • systemjs.config.js

    – システム構成(バージョン2)


  • package.json

    – ノードモジュールの依存関係(バージョン2以降)


  • tsconfig.json

    – ルートレベルのTypescript設定(バージョン2

以降)
**

tsconfig.app.json

– アプリケーションレベルのタイプスクリプト設定

(バージョン4以降)
**


_。angular –


cli

.json_

– Angular CLI設定(バージョン4および5)


  • angular.json

    – Angular CLI設定(バージョン6以降)


4ログインページ


4.1. AngularJS

を使用する


index.html

ファイルを作成し、それに関連する依存関係を追加しましょう。

<html ng-app="app">
<body>
    <div ng-view></div>

    <script src="//code.jquery.com/jquery-3.1.1.min.js"></script>
    <script src="//code.angularjs.org/1.6.0/angular.min.js"></script>
    <script src="//code.angularjs.org/1.6.0/angular-route.min.js"></script>
    <script src="app.js"></script>
    <script src="home/home.controller.js"></script>
    <script src="login/login.controller.js"></script>
</body>
</html>

これは単一ページのアプリケーションなので、ルーティングロジックに基づいて

ng-view

属性を持つすべての子コンポーネントがdiv要素に追加されます。

次に、URLからコンポーネントへのマッピングを定義する

app.js

を作成しましょう。

(function () {
    'use strict';

    angular
        .module('app',['ngRoute'])
        .config(config)
        .run(run);

    config.$inject =['$routeProvider', '$locationProvider'];
    function config($routeProvider, $locationProvider) {
        $routeProvider.when('/', {
            controller: 'HomeController',
            templateUrl: 'home/home.view.html',
            controllerAs: 'vm'
        }).when('/login', {
            controller: 'LoginController',
            templateUrl: 'login/login.view.html',
            controllerAs: 'vm'
        }).otherwise({ redirectTo: '/login' });
    }

    run.$inject =['$rootScope', '$location', '$http', '$window'];
    function run($rootScope, $location, $http, $window) {
        var userData = $window.sessionStorage.getItem('userData');
        if (userData) {
            $http.defaults.headers.common['Authorization']              = 'Basic ' + JSON.parse(userData).authData;
        }

        $rootScope
        .$on('$locationChangeStart', function (event, next, current) {
            var restrictedPage
              = $.inArray($location.path(),['/login']) === -1;
            var loggedIn
              = $window.sessionStorage.getItem('userData');
            if (restrictedPage && !loggedIn) {
                $location.path('/login');
            }
        });
    }
})();

ログインコンポーネントは、

login.controller.js

と__login.view.htmlの2つのファイルで構成されています。

最初のものを見てみましょう。

<h2>Login</h2>
<form name="form" ng-submit="vm.login()" role="form">
    <div>
        <label for="username">Username</label>
        <input type="text" name="username"
          id="username" ng-model="vm.username" required/>
        <span ng-show="form.username.$dirty
          && form.username.$error.required">Username is required</span>
    </div>
    <div>
        <label for="password">Password</label>
        <input type="password"
          name="password" id="password" ng-model="vm.password" required/>
        <span ng-show="form.password.$dirty
          && form.password.$error.required">Password is required</span>
    </div>
    <div class="form-actions">
        <button type="submit"
          ng-disabled="form.$invalid || vm.dataLoading">Login</button>
    </div>
</form>

そして2番目のもの:

(function () {
    'use strict';
    angular
        .module('app')
        .controller('LoginController', LoginController);

    LoginController.$inject =['$location', '$window', '$http'];
    function LoginController($location, $window, $http) {
        var vm = this;
        vm.login = login;

        (function initController() {
            $window.localStorage.setItem('token', '');
        })();

        function login() {
            $http({
                url: 'http://localhost:8082/login',
                method: "POST",
                data: {
                    'userName': vm.username,
                    'password': vm.password
                }
            }).then(function (response) {
                if (response.data) {
                    var token
                      = $window.btoa(vm.username + ':' + vm.password);
                    var userData = {
                        userName: vm.username,
                        authData: token
                    }
                    $window.sessionStorage.setItem(
                      'userData', JSON.stringify(userData)
                    );
                    $http.defaults.headers.common['Authorization']                      = 'Basic ' + token;
                    $location.path('/');
                } else {
                    alert("Authentication failed.")
                }
            });
        };
    }
})();

コントローラーは、ユーザー名とパスワードを渡すことによってRESTサービスを呼び出します。認証が成功すると、ユーザー名とパスワードをエンコードし、エンコードされたトークンを将来の使用に備えてセッションストレージに保存します。

loginコンポーネントと同様に、homeコンポーネントも


_home.view.html

_



という2つのファイルで構成されています。

<h1>Hi {{vm.user}}!</h1>
<p>You're logged in!!</p>
<p><a href="#!/login" class="btn btn-primary" ng-click="logout()">Logout</a></p>

そして

home.controller.js:

(function () {
    'use strict';
    angular
        .module('app')
        .controller('HomeController', HomeController);

    HomeController.$inject =['$window', '$http', '$scope'];
    function HomeController($window, $http, $scope) {
        var vm = this;
        vm.user = null;

        initController();

        function initController() {
            $http({
                url: 'http://localhost:8082/user',
                method: "GET"
            }).then(function (response) {
                vm.user = response.data.name;
            }, function (error) {
                console.log(error);
            });
        };

        $scope.logout = function () {
            $window.sessionStorage.setItem('userData', '');
            $http.defaults.headers.common['Authorization']= 'Basic';
        }
    }
})();

ホームコントローラーは

Authorization

ヘッダーを渡すことによってユーザーデータを要求します。トークンが有効な場合にのみ、RESTサービスはユーザーデータを返します。

それではAngularアプリケーションを実行するために

http-server

をインストールしましょう。

npm install http-server --save

これがインストールされたら、コマンドプロンプトでプロジェクトのルートフォルダを開き、次のコマンドを実行します。

http-server -o


4.2. Angularバージョン2、4、5を使用する

バージョン2の

index.html

はAngularJSバージョンとは若干異なります。

<!DOCTYPE html>
<html>
<head>
    <base href="/"/>
    <script src="node__modules/core-js/client/shim.min.js"></script>
    <script src="node__modules/zone.js/dist/zone.js"></script>
    <script src="node__modules/systemjs/dist/system.src.js"></script>

    <script src="systemjs.config.js"></script>
    <script>
        System.import('app').catch(function (err) { console.error(err); });
    </script>
</head>
<body>
    <app>Loading...</app>
</body>
</html>


main.ts

はアプリケーションの主なエントリポイントです。それはアプリケーションモジュールをブートストラップし、その結果、ブラウザはログインページをロードします。

platformBrowserDynamic().bootstrapModule(AppModule);


app.routing.ts

はアプリケーションルーティングを担当します。

const appRoutes: Routes =[    { path: '', component: HomeComponent },
    { path: 'login', component: LoginComponent },
    { path: '** ** ', redirectTo: '' }];

export const routing = RouterModule.forRoot(appRoutes);


app.module.ts

はコンポーネントを宣言し、関連モジュールをインポートします。

@NgModule({
    imports:[        BrowserModule,
        FormsModule,
        HttpModule,
        routing
   ],
    declarations:[        AppComponent,
        HomeComponent,
        LoginComponent
   ],
    bootstrap:[AppComponent]})

export class AppModule { }

単一ページのアプリケーションを作成しているので、すべての子コンポーネントを追加するルートコンポーネントを作成しましょう。

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html'
})

export class AppComponent { }


app.component.html

には

<router-outlet>

タグしかありません。 Angularはこのタグをロケーションルーティングメカニズムに使用します。

それでは、__login.component.tsにログインコンポーネントとそれに対応するテンプレートを作成しましょう。

@Component({
    selector: 'login',
    templateUrl: './app/login/login.component.html'
})

export class LoginComponent implements OnInit {
    model: any = {};

    constructor(
        private route: ActivatedRoute,
        private router: Router,
        private http: Http
    ) { }

    ngOnInit() {
        sessionStorage.setItem('token', '');
    }

    login() {
        let url = 'http://localhost:8082/login';
        let result = this.http.post(url, {
            userName: this.model.username,
            password: this.model.password
        }).map(res => res.json()).subscribe(isValid => {
            if (isValid) {
                sessionStorage.setItem(
                  'token',
                  btoa(this.model.username + ':' + this.model.password)
                );
                this.router.navigate(['']);
            } else {
                alert("Authentication failed.");
            }
        });
    }
}

最後に、

login.component.html

を見てみましょう。

<form name="form" (ngSubmit)="f.form.valid && login()" #f="ngForm" novalidate>
    <div[ngClass]="{ 'has-error': f.submitted && !username.valid }">
        <label for="username">Username</label>
        <input type="text"
          name="username"[(ngModel)]="model.username"
            #username="ngModel" required/>
        <div ** ngIf="f.submitted
          && !username.valid">Username is required</div>
    </div>
    <div[ngClass]="{ 'has-error': f.submitted && !password.valid }">
        <label for="password">Password</label>
        <input type="password"
          name="password"[(ngModel)]="model.password"
            #password="ngModel" required/>
        <div ** ngIf="f.submitted
          && !password.valid">Password is required</div>
    </div>
    <div>
        <button[disabled]="loading">Login</button>
    </div>
</form>


4.3. Angular 6

を使う

Angularチームはバージョン6でいくつかの機能強化を行いました。これらの変更により、私たちの例も他のバージョンと比べて少し異なります。この例でバージョン6に関する唯一の変更点は、サービス呼び出し部分にあります。


  • HttpModule

    の代わりに、バージョン6は



    @angular/common/http://から

    HttpClientModule__をインポートします。

サービス呼び出しの部分も、以前のバージョンとは少し異なります。

this.http.post<Observable<boolean>>(url, {
    userName: this.model.username,
    password: this.model.password
}).subscribe(isValid => {
    if (isValid) {
        sessionStorage.setItem(
          'token',
          btoa(this.model.username + ':' + this.model.password)
        );
    this.router.navigate(['']);
    } else {
        alert("Authentication failed.")
    }
});


5結論

Angularを使用してSpring Securityのログインページを実装する方法を学習しました。バージョン4以降、Angular CLIプロジェクトを利用して開発とテストを容易にすることができます。

ここで説明した例はすべてhttps://github.com/eugenp/tutorials/tree/master/spring-security-angular[Githubプロジェクト]にあります。