Keycloakでpairwiseを実現してみる

初版: 2021/12/4

著者: 田畑義之, 株式会社日立製作所 (GitHubアカウント: [@y-tabata](https://github.com/y-tabata))

# はじめに

今回は、IAM(Identity and Access Management)製品で、OAuth 2.0/OpenID Connect 1.0 (OIDC)の認可サーバ(OpenID Provider)としても利用可能なKeycloakというOSSを使って、OIDCに定義されている"[pairwise](https://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes)"を実現してみます。

なお、Keycloakに関する詳細は、Think ITの連載「[Keycloakで実現するAPIセキュリティ](https://thinkit.co.jp/series/9721)」をご参照ください。

本記事は、あくまで執筆者の見解であり、日立製作所の公式なドキュメントではありません。

# pairwiseとは

pairwiseを説明する前に、まずはOIDCについて簡単に説明します。

OIDCでは、各サービス(Relying Party: RP)はエンドユーザの認証を認可サーバ(OpenID Provider: OP)に委譲します。OPはエンドユーザを認証し、その認証結果としてIDトークンを各サービスに発行します。IDトークンはJWTの形式をとっており、その中にさまざまな情報(クレーム)を含んでいます。今回pairwiseを実現する上で着目するのは、Subject(sub)クレームというクレームで、認証したエンドユーザの識別子を格納するクレームです。

![pairwise1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267112/82878dc7-04d2-4426-85e2-6f88245bbc4e.png)

このsub識別子のタイプとして、OIDCでは以下の2つのタイプを定義しています。

- public<br>
  全てのRPに対して、**同一**のsub値を提供する。上図の例はこちらに当たります。
- pairwise<br>
  各々のRPに対して、**異なる**sub値を提供する。

ではなぜpairwiseが必要なのでしょうか。

sub識別子のタイプがpublicの場合に問題となるケースを見ていきましょう。sub識別子のタイプがpublicの場合に問題となるのは、複数のサービスが結託した場合に、**名寄せ**(同一人物のデータの統合)ができてしまうという点です。各サービスにとっては、名寄せすることによって、1つのサービスで収集するよりも多くの情報を入手することができ、エンドユーザに対する効率的なサービス提供が可能になります。

![pairwise2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267112/50ef05b6-ca3a-69a5-7c96-66763848fc76.png)

一方、昨今、世界中でエンドユーザのプライバシー保護がより強く叫ばれている中で、こういった名寄せをエンドユーザの同意なしに行ったり、あるいはエンドユーザの同意なしに行える状態にあったりすること自体を問題視する気運があります。こういった場合に、下図のように各サービスに対して、異なるsub値を持つIDトークンを発行するという手段( = **pairwise**)が有効となり、これを採用しているシステムも少なくありません。

![pairwise3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267112/d97718f8-a597-60f5-6524-972463dc0e4d.png)

# Keycloakでpairwiseを実現してみる

それでは実際に、Keycloakでpairwiseを実現してみましょう。今回はKeycloakのバージョン15.0.2を使います。

まずは2つのサービス(elephantとgiraffe)を作成し、IDトークンを発行してみましょう。

```json:elephant用IDトークン
{
  "jti": "13ca5a87-c4ce-4b16-8263-e0f46713fe34",
  "iss": "http://localhost:8080/auth/realms/test",
  "aud": "elephant",
  "sub": "816c0bf8-24f8-431b-b36f-9437b4ea942c",
  "typ": "ID",
  "azp": "elephant",
  "preferred_username": "test",
  ...
}
```

```json:giraffe用IDトークン
{
  "jti": "154328ea-46bd-4ad9-b0e7-848c9828a684",
  "iss": "http://localhost:8080/auth/realms/test",
  "aud": "giraffe",
  "sub": "816c0bf8-24f8-431b-b36f-9437b4ea942c",
  "typ": "ID",
  "azp": "giraffe",
  "preferred_username": "test",
  ...
}
```

デフォルトではsub識別子のタイプがpublicなので、両サービスのsub値が同一(816c0bf8-24f8-431b-b36f-9437b4ea942c)です。

それでは、Keycloakでpairwiseを設定していきます。Keycloakにおけるpairwiseの設定は、各クライアントのマッパーの設定の中で、新しく"Pairwise subject identifier"というタイプのマッパーを作成するだけです。

![keycloak-pairwise.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267112/87b6a4b8-9ba1-b228-12f0-ec1f284a2060.png)

両サービスにpairwiseの設定をした後、再度IDトークンを発行してみましょう。

```json:elephant用IDトークン
{
  "jti": "76f10fb1-9594-4d95-885c-fc92110cd5a7",
  "iss": "http://localhost:8080/auth/realms/test",
  "aud": "elephant",
  "sub": "09fc511e-8f96-31f5-bdff-4d9e894fcd26",
  "typ": "ID",
  "azp": "elephant",
  "preferred_username": "test",
  ...
}
```

```json:giraffe用IDトークン
{
  "jti": "56238788-9b55-4d74-95ae-2025f9416551",
  "iss": "http://localhost:8080/auth/realms/test",
  "aud": "giraffe",
  "sub": "583b3185-8c7e-3fe7-bd8b-1a9776e38aa4",
  "typ": "ID",
  "azp": "giraffe",
  "preferred_username": "test",
  ...
}
```

sub識別子のタイプがpairwiseとなり、両サービスのsub値が異なる値(09fc511e-8f96-31f5-bdff-4d9e894fcd26と583b3185-8c7e-3fe7-bd8b-1a9776e38aa4)になったことがわかります。

ちなみにKeycloakではpairwiseの場合、以下のような方法でsub値を算出します。

sub = SHA-256 ( sector_identifier || local_account_id || salt )

このsub値の算出ロジックは、SPIになっていますので、好みのロジックにカスタマイズすることもできます。

# おわりに

今回はKeycloakでpairwiseを実現する方法をご紹介しました。Keycloakでのこのような細かな要件の実現方法は意外と出回っていないので参考になればと思います。