Angular付きのSpring Securityログインページ
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プロジェクト]にあります。