Keycloak で Angular の AuthGuard をやってみよう
以前の記事("【2019年10月版】Quarkus の REST API を KeyCloak で SSO するデモを docker-compose で動かしてみた。")にて、Quarkus の REST API と Keycloak の連携するデモを動かしてみましたが、連携が非常に簡単なのでとても驚き「こっ・・・これはイケるッ!!」となった次第でございます。
バックエンド側の REST API が Keycloak 対応バッチリなのであればフロント側も確認しないとダメでしょう?ということで、Anuglar の Router Guard 機能を使って Keycloak での AuthGuard にトライしてみたいと思います。
対象環境
Keycloak : 7.0.1
Angular: 7.2.0
ng new
コマンドで新規生成した Angular プロジェクトに admin
とuser
ロールでそれぞれアクセスできるページを用意し、Keycloak で設定したロールがAuthGuardで利かせられるかどうかを確かめてみたいと思います。
1. Angular プロジェクトの作成
$ ng new ng-keycloak-sample
...
$ cd ng-keycloak-sample
いくつか質問されますが、"Router"についての質問には Y
として追加してください。あとは適当でOKです!
さて、このプロジェクトの中で作業を進めてまいります。
2. Keycloak パッケージの追加
keycloak-angular を追加します。バージョンは使用する Keycloak に合わせます。
$ npm i keycloak-angular@7.0.1
$ npm i keycloak-js@7.0.1
keycloak-js は peer dependency
なので別途、インストールする必要があります。こちらもバージョンを実際に使用する Keycloak に合わせます。
3. 適当ページと Router の作成
それでは、実際のコンポーネントのコーディングに入ります。
3-1. ページコンポーネントの作成
以下のコマンドでコンポーネントを生成してしまいます。
$ ng g c main # -> トップページ
...
$ ng g c pages/config # -> Admin エリア
...
$ ng ng c pages/dashboard # -> user エリア
で共通エリア(?)、Userエリア、Adminエリアの3つを(適当に)ご用意いたしました。
今回は各コンポーネントの実装を先に行ってしまって、パスとGuardのルーティング設定は後の工程で行います。
まず共通エリアとなるmain.component.html
ですが、ここでは各ページへのリンクとログオフボタンを用意しておきます。
<p>
main works!
</p>
<li><a [routerLink]="[ '/dashboard']">Here is User Area</a></li>
<li><a [routerLink]="[ '/config']">Here is Admin Area</a></li>
<li><button (click)="logOut()">Log Out</button></li>
main.component.ts
では KeycloakService
とログオフの機能を追加します。
import { Component, OnInit } from '@angular/core';
import { KeycloakService } from 'keycloak-angular';
@Component({
selector: 'app-main',
templateUrl: './main.component.html',
styleUrls: ['./main.component.scss']
})
export class MainComponent implements OnInit {
constructor(private keycloakAngular: KeycloakService) { }
ngOnInit() {
}
async logOut() {
await this.keycloakAngular.logout("http://localhost:4200/");
}
}
コンストラクタでKeycloakService
を受け取っておきます。
logOut
メソッドでのログアウト後のリダイレクトには "KeyCloakからみたURL"を入れます。とりあえず Angular の開発サーバーである localhost:4200
をリダイレクト先にしておきます。
Adminエリアであるconfig
コンポーネントと、Userエリアであるdashboard
コンポーネントでも同様に、メインへ戻るリンクとLog Out
ボタンを設置しておきます。ここではソースコードは割愛させてください。
3-2. Router の設定
いよいよ本丸その1のルーターの設定です。ここでAuthGuardとロールの設定を行います。
const routes: Routes = [
{
path: '',
component: MainComponent
},
{
path: 'config',
component: ConfigComponent,
canActivate: [CanAuthenticationGuard],
data: { roles: ['admin'] }
},
{
path: 'dashboard',
component: DashboardComponent,
canActivate: [CanAuthenticationGuard],
data: { roles: ['user'] }
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
まず MainComponent
はガードなしで、誰でもアクセス可能=Keycloakのログインなしで入場可能とします。
次の ConfigComponent
は CanAuthenticationGuard
というガードを設定し、data
にて { roles: ['admin'] }
を指定して、CanAuthenticationGuard
の中でチェックするべきロールを宣言します。
ここでは admin
と宣言し、CanAuthenticationGuard
内で admin
ロールを持つユーザーのみがアクセス可能とします。
同様に DashboardComponent
では data
に { roles: ['user'] }
を宣言し、CanAuthenticationGuard
内で user
ロールを持つユーザーのみがアクセス可能とします。
canActivate
でガードの指定と、data
で許可するロールを宣言する、というのがここでのキモです。
4. keycloak-angular(Keycloak-js) の組み込み
続いて keycloak-angular の手当てを組み込んでいきます。
今回の手順はほぼ本家リポジトリのREADME.md に従った手順となります。
4-1. APP_INITIALIZER を使った初期化
まず Keycloakクライアントの初期化処理ですが、今回は APP_INITIALIZER
を使ったパターンで行ってみます。
初期化のオプション少ないので app.module.ts
に直書きでもいいかもしれませんが、一応、切り出しておきます。
export function initializer(keycloak: KeycloakService): () => Promise<any> {
return (): Promise<any> => new Promise(async (resolve, reject) => {
try {
await keycloak.init({
config: {
url: 'http://localhost:8180/auth',
realm: 'Angular Sample',
clientId: 'ng-keycloak-sample'
},
});
resolve();
} catch (error) { }
});
}
Keycloak のアドレス、Keycloak で定義する Realm と ClientId を指定してクライアントの初期化を行います。
この initializer
関数を export しておきます。
そして app.module.ts
で export した initializer
関数を使用して KeycloakService
を初期化します。
...
providers: [
{
provide: APP_INITIALIZER,
useFactory: initializer,
multi: true,
deps: [KeycloakService]
}],
bootstrap: [AppComponent]
})
export class AppModule { }
上記のようになります。
4-2. AuthGuard の実装
こちらの本家のAuthGuardの説明より、AuthGuardを実装します。
クラス名もそのまま拝借したのですが、、ちょおっとアレですね。。。
...
@Injectable({
providedIn: 'root'
})
export class CanAuthenticationGuard extends KeycloakAuthGuard implements CanActivate {
constructor(protected router: Router, protected keycloakAngular: KeycloakService) {
super(router, keycloakAngular);
}
isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
return new Promise((resolve, reject) => {
console.log(this.roles);
if (!this.authenticated) {
this.keycloakAngular.login()
.catch(e => console.error(e));
return reject(false);
}
const requiredRoles: string[] = route.data.roles;
console.log("required:%o", requiredRoles);
if (!requiredRoles || requiredRoles.length === 0) {
return resolve(true);
} else {
if (!this.roles || this.roles.length === 0) {
resolve(false);
}
if (requiredRoles.every(role => this.roles.indexOf(role) > -1)) {
resolve(true);
}
else {
window.alert("Auth Blocked!")
}
}
});
}
}
route.data.roles
で上記のapp-routing.module.ts
で宣言した Routes
の data: {roles:[...]}
が渡されます。this.roles にはログイン後に Keycloak から渡されたロールのリストが入ってきますので、これとのマッチングを行います。
現状では、ログイン後に権限がない場合は window.alert("Auth Blocked!")
とアラートが出されるようにしております。
実は、コーディングとしては以上となります!AuthGuard の実装は実際のプロジェクトではいろいろ考慮が必要かと思います。。。
5. Keycloak の用意
続きまして Keycloak サーバーの準備に取り掛かります。
5-1. Keycloak with docker-compose
Keycloak はいつも通り、docker-composeで用意します。
version : "3"
services:
keycloak:
image: jboss/keycloak:7.0.1
container_name: keycloak
restart: always
ports:
- 8180:8180
environment:
- KEYCLOAK_USER=admin
- KEYCLOAK_PASSWORD=admin
volumes:
- ./ng-keycloak-sample-realm.json:/config/ng-keycloak-sample-realm.json
command: >
-b 0.0.0.0
-Djboss.http.port=8180
-Dkeycloak.migration.action=import
-Dkeycloak.migration.provider=singleFile
-Dkeycloak.migration.file=/config/ng-keycloak-sample-realm.json
-Dkeycloak.migration.strategy=OVERWRITE_EXISTING
tty: true
Realm:Angular Sample
と clientId:ng-keycloak-sample
を設定した json はご用意いたしました!
以下のコマンドで起動します。
$ docker-compose up
5-2. ユーザーの追加
ユーザーのリストを含む設定値の Export がWEBコンソールからではできないようなので、ユーザーの登録とロールの割り当ては手作業でお願いします。すいません。。。
ブラウザから http://localhost:8180
に admin/admin
でログインし、手作業でユーザーの登録をお願いします。
以下のサーバー管理ガイドにて詳しく解説されておりますのでご覧ください。
また、以下の記事もご覧ください。
今回は "user001/user001" に "user" ロール、"user002/user02" に "user" ロールと "admin" ロールを付与して動作確認をしてみたいともいます。
ちなみに、結果はこんな感じになっております。
user001
のロールはクライアントng-keycloak-sample
では user
を割り当てました。
同様に user002
では user
と admin
のロールをアサインしてください。
アサインが終わったら Keycloakからsignout
しておいてください。本物のadmin
でログインした情報が残ってしまいます!
6. 動作確認
それではいよいよ、Angular の開発サーバーを起動します。
$ ng serve
で、ブラウザから http://localhost:4200
を開いてください。
MainComponentが表示されています。この時点では Keycloak のログインに行っていないです。(KeycloakとAngularのサンプルを検索するとアプリ丸ごとのログインするサンプルが多いので。。。)
さて、Here is User Area
をクリックしてみましょう。
はい!AuthGuard でめでたく Keycloakのログインに遷移しました!
ここで "user001/user001" でログインすると、いったんMainComponentに戻ってきます。(このあたりの動作は改善の余地ありで。。。)
で、再度Here is User Area
をクリックすると・・・
dashboard works!
の文字が眩しい、DashBoardComponent
に無事に遷移しました!
さて、Back To Main
クリックで MainComponent にいったん戻り、Here is Admin Area
をクリックすると・・・
ブロック!! めでたく遷移がブロックされました!
user001
は admin
ロールは持っていないのでブロックされています。
さて、同様に user002
でアクセスしてみましょうか。
いったん、Log Out をクリックします。
はい、MainComponent に何の変哲もない戻りました。
そして再度、Here is Admin Area
をクリックすると・・・
KeyCloakのログイン画面です。ちゃんとログアウトされていたようですね。
さてここで user002/user002
でログインします。
はい、再びのMainComponent です。
さて、ここで Here is Admin Area
をクリックすると・・・
config work! 来ましたー!ConfigComponent が無事に表示されました。
Keycloak と連携したAuthGuard、ばっちり機能しているようです!
まとめ
今回は Angular アプリ全体ではなく、一部のページのみの AuthGuard を Kyecloak と連携して行ってみる実験でした。
AuthGuard のページ数が増えるとちょっとコンポーネントの階層など工夫が必要かと思いますが、それほど実装負荷も大きくなく、ロールでの切り替えもバッチリ動くことが確認できました。
これはアリ、ではないでしょうか!?
今回作成したサンプルアプリは github に上げております!
本日は以上といたします!