LoginSignup
2
1

More than 3 years have passed since last update.

【2019年11月版】Keycloak ニテ Angular ヲ AuthGuard セヨ

Last updated at Posted at 2019-11-06

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 プロジェクトに adminuserロールでそれぞれアクセスできるページを用意し、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 ですが、ここでは各ページへのリンクとログオフボタンを用意しておきます。

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 とログオフの機能を追加します。

main.component.ts
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とロールの設定を行います。

app-routing.module.ts
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のログインなしで入場可能とします。
次の ConfigComponentCanAuthenticationGuard というガードを設定し、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 に直書きでもいいかもしれませんが、一応、切り出しておきます。

util/init-app.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 を初期化します。

app.module.ts
...
  providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: initializer,
      multi: true,
      deps: [KeycloakService]
    }],
  bootstrap: [AppComponent]
})
export class AppModule { }

上記のようになります。

4-2. AuthGuard の実装

こちらの本家のAuthGuardの説明より、AuthGuardを実装します。
クラス名もそのまま拝借したのですが、、ちょおっとアレですね。。。

service/CanAuthenticationGuard.ts
...
@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で宣言した Routesdata: {roles:[...]} が渡されます。this.roles にはログイン後に Keycloak から渡されたロールのリストが入ってきますので、これとのマッチングを行います。
現状では、ログイン後に権限がない場合は window.alert("Auth Blocked!") とアラートが出されるようにしております。

実は、コーディングとしては以上となります!AuthGuard の実装は実際のプロジェクトではいろいろ考慮が必要かと思います。。。

5. Keycloak の用意

続きまして Keycloak サーバーの準備に取り掛かります。

5-1. Keycloak with docker-compose

Keycloak はいつも通り、docker-composeで用意します。

docker-comopse.yml
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:8180admin/admin でログインし、手作業でユーザーの登録をお願いします。
以下のサーバー管理ガイドにて詳しく解説されておりますのでご覧ください。

また、以下の記事もご覧ください。

今回は "user001/user001" に "user" ロール、"user002/user02" に "user" ロールと "admin" ロールを付与して動作確認をしてみたいともいます。

ちなみに、結果はこんな感じになっております。

Keycloak Admin Console User001.png

user001 のロールはクライアントng-keycloak-sample では user を割り当てました。
同様に user002 では useradmin のロールをアサインしてください。

アサインが終わったら Keycloakからsignoutしておいてください。本物のadminでログインした情報が残ってしまいます!

6. 動作確認

それではいよいよ、Angular の開発サーバーを起動します。

$ ng serve

で、ブラウザから http://localhost:4200 を開いてください。

NgKeycloakSample.png

MainComponentが表示されています。この時点では Keycloak のログインに行っていないです。(KeycloakとAngularのサンプルを検索するとアプリ丸ごとのログインするサンプルが多いので。。。)

さて、Here is User Area をクリックしてみましょう。

Log in to Angular Sample.png

はい!AuthGuard でめでたく Keycloakのログインに遷移しました!
ここで "user001/user001" でログインすると、いったんMainComponentに戻ってきます。(このあたりの動作は改善の余地ありで。。。)
で、再度Here is User Area をクリックすると・・・
NgKeycloakSample (1).png

dashboard works! の文字が眩しい、DashBoardComponent に無事に遷移しました!
さて、Back To Main クリックで MainComponent にいったん戻り、Here is Admin Area をクリックすると・・・

image.png

ブロック!! めでたく遷移がブロックされました!
user001admin ロールは持っていないのでブロックされています。
さて、同様に user002 でアクセスしてみましょうか。

いったん、Log Out をクリックします。

NgKeycloakSample.png

はい、MainComponent に何の変哲もない戻りました。
そして再度、Here is Admin Area をクリックすると・・・

Log in to Angular Sample.png

KeyCloakのログイン画面です。ちゃんとログアウトされていたようですね。
さてここで user002/user002 でログインします。

NgKeycloakSample.png

はい、再びのMainComponent です。

さて、ここで Here is Admin Area をクリックすると・・・
NgKeycloakSample (2).png

config work! 来ましたー!ConfigComponent が無事に表示されました。

Keycloak と連携したAuthGuard、ばっちり機能しているようです!

まとめ

今回は Angular アプリ全体ではなく、一部のページのみの AuthGuard を Kyecloak と連携して行ってみる実験でした。
AuthGuard のページ数が増えるとちょっとコンポーネントの階層など工夫が必要かと思いますが、それほど実装負荷も大きくなく、ロールでの切り替えもバッチリ動くことが確認できました。
これはアリ、ではないでしょうか!?

今回作成したサンプルアプリは github に上げております!

本日は以上といたします!

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1