Keycloakのユーザープロパティ/属性と連携アプリのユーザー情報のマッピング仕様

# 概要

Keycloakで開発用オンプレブラウザアプリ類をSSO化するにあたり、Keycloak上のユーザープロパティ/ユーザー属性が各アプリのユーザー情報にどのようにマッピングされているか、またどの程度調整が可能かを（軽めに）調査した。
調査対象とちょっとした結論は以下の通り。

| App         | Version       | 認証方式      | 表示名指定 | 同期 |
| ----------- | ------------- | ------------- | ---------- | ---- |
| GitLab      | 13.9.3        | SAML          | 可         |      |
| Gitea       | 1.13.4        | OIDC          | 不可       |      |
| CodiMD      | 2.3.2         | SAML          | 可         | あり |
| Wiki.js     | 2.5           | SAML          | 可         |      |
| GROWI       | 4.2.12        | SAML          | 制限あり   |      |
| Mattermost  | 5.32.1        | GitLab OAuth2 | 可         | あり |
| rocket.chat | 3.12.1        | SAML          | 可         |      |
| Wekan       | 5.05.0        | OIDC          | 可         |      |
| TAIGA       | 6.0.7         | GitLab OAuth2 | 制限あり   |      |
| PENPOT      | 1.3.0-alpha-1 | GitLab OAuth2 | 可         |      |
| NextCloud   | 12.0.0.13     | SAML          | 可         | あり |

※空欄は未調査

## Keycloak

バージョン: 12.0.4 (Docker)

### ユーザープロパティ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/0f3988fa-a5e1-2b84-0165-e85bc916ce08.png)

### ユーザー属性

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/fddf6f0e-42cf-8ac9-6309-5d70ad928216.png)

※ `longId` はMattermostで使用する（後述）

## GitLab

- バージョン: 13.9.3 (Docker, [sameersbn版](https://hub.docker.com/r/sameersbn/gitlab))
- 認証方式: SAML

### KeyCloak側のマッピング

| Type | Prop/Attr | SAML Attr  |
| ---- | --------- | ---------- |
| Prop | email     | email      |
| Prop | username  | username   |
| Prop | firstName | first_name |
| Prop | lastName  | last_name  |
| Attr | nickname  | name       |

### GitLab側のマッピング

```yaml:docker-compose.yml
︙
- OAUTH_SAML_ATTRIBUTE_STATEMENTS_EMAIL=email
- OAUTH_SAML_ATTRIBUTE_STATEMENTS_NAME=name
- OAUTH_SAML_ATTRIBUTE_STATEMENTS_USERNAME=username
- OAUTH_SAML_ATTRIBUTE_STATEMENTS_FIRST_NAME=first_name
- OAUTH_SAML_ATTRIBUTE_STATEMENTS_LAST_NAME=last_name
︙
```

### 結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/5a41c556-8d94-0d80-1298-15e04528a01c.png)

| DB Field    | SAML Attr  | 変更  | 備考         |
| ----------- | ---------- | :---: | ------------ |
| username    | username   | 不可  |              |
| name        | name       |  可   | 表示名       |
| email       | email      |  可   |              |

- ログインIDやmention等に使われるusernameと表示名であるnameが分離しており、それぞれマッピングで個別に紐付けられる
- usernameはKeycloakでも変更できないため必ず等しくなる
- first_nameとlast_nameは使われない模様

## Gitea

- バージョン: 1.13.4 (Docker)
- 認証方式: OpenID Connect

### KeyCloak側のマッピング

なし

### Gitea側のマッピング

なし

<details><summary>claimの初期化コード</summary><div>

```go:openidConnect.go
const (
	// Standard Claims http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
	// fixed, cannot be changed
	subjectClaim  = "sub"
	expiryClaim   = "exp"
	audienceClaim = "aud"
	issuerClaim   = "iss"

	PreferredUsernameClaim = "preferred_username"
	EmailClaim             = "email"
	NameClaim              = "name"
	NicknameClaim          = "nickname"
	PictureClaim           = "picture"
	GivenNameClaim         = "given_name"
	FamilyNameClaim        = "family_name"
	AddressClaim           = "address"

	// Unused but available to set in Provider claims
	MiddleNameClaim          = "middle_name"
	ProfileClaim             = "profile"
	WebsiteClaim             = "website"
	EmailVerifiedClaim       = "email_verified"
	GenderClaim              = "gender"
	BirthdateClaim           = "birthdate"
	ZoneinfoClaim            = "zoneinfo"
	LocaleClaim              = "locale"
	PhoneNumberClaim         = "phone_number"
	PhoneNumberVerifiedClaim = "phone_number_verified"
	UpdatedAtClaim           = "updated_at"

	clockSkew = 10 * time.Second
)
```

</div></details>

### 結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/38abc2fd-a2d9-55e6-df0f-6b59ee7495da.png)

なぜかnicknameが入ってくる。以下Keycloakのユーザー属性からnicknameを削除して再実行。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/048b8c08-4912-be77-46fd-7cbf5fee31b3.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/67233bc2-483c-5858-6a53-60f6d3d4bed8.png)

| DB Field | Claim                              | 変更  | 備考 |
| -------- | ---------------------------------- | :---: | ---- |
| name     | nickname &#124; preferred_username | 不可  |      |
| email    | email                              |  可   |      |

- 謎のnickname推し
- **Keycloakのユーザー情報にnickname属性が存在していると優先してusernameに使おうとする**ため、初回認証時のユーザー作成画面にnicknameが入るのを回避したい場合はそもそもnickname属性を作成してはいけない
- Keycloakでユーザー属性やマッパーにあれこれ設定してもフルネームには何も入れられない

## CodiMD

- バージョン: 2.3.2 (Docker)
- 認証方式: SAML

### Keycloak側のマッピング

| Type | Prop/Attr | SAML Attr  |
| ---- | --------- | ---------- |
| Prop | id        | id         |
| Prop | email     | email      |
| Prop | username  | username   |

### CodiMD側のマッピング

```yaml:docker-compose.yml
︙
- CMD_SAML_ATTRIBUTE_ID=id
- CMD_SAML_ATTRIBUTE_USERNAME=username
- CMD_SAML_ATTRIBUTE_EMAIL=email
︙
```

### 結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/31e829c1-cb50-e15c-c918-40db4a5ec78b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/e6f4adc4-4578-5b81-bf8d-f5b6d9f1a20e.png)

| DB Field         | SAML Attr | 変更  | 備考                     |
| ---------------- | --------- | :---: | ------------------------ |
| profileId        | id        | 不可  | 不可視, 識別に使用       |
| profile.username | username  | 不可  | 表示名, ログイン時に更新 |
| profile.emails   | email     | 不可  | 不可視                   |

- **表示名は認証の度に更新**される
- もちろんusername以外のSAML Attributeを割り当てることもできる
- 運用中にマッピングを変更しても問題ない、内容は次の認証時に反映される

KeycloakのdisplayNameをCodiMDのusernameに割り当てた場合:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/e5e05eb9-adeb-5749-d1d2-d78afe2fc6b7.png)

## Wiki.js

- バージョン: 2.5 (Docker)
- 認証方式: SAML

### KeyCloak側のマッピング

| Type | Prop/Attr | SAML Attr  |
| ---- | --------- | ---------- |
| Prop | id        | id         |
| Prop | email     | email      |
| Prop | username  | username   |

### Wiki.js側のマッピング

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/2437a357-c466-9afa-2053-7633d2eb7758.png)

### 結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/c35007bb-2892-1205-7f94-499694306a16.png)

| DB Field   | SAML Attr | 変更  | 備考               |
| ---------- | --------- | :---: | ------------------ |
| providerId | id        | 不可  | 不可視, 識別に使用 |
| name       | username  |  可   | 表示名             |
| email      | email     |  可   |                    |

- 表示名に任意のプロパティないし属性を紐付けられる
- Wiki.jsは標準ではメールアドレスとパスワードでログインする仕組みのためログインID的なものは存在しない
- 因みにWiki.jsにはKeycloak用のOAuth2の認証設定も存在する

Display Nameへのマッピングをnicknameに変更した場合:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/33f76cf8-37d4-50b0-da40-963db77f60c4.png)

## GROWI

- バージョン: 4.2.12 (Docker)
- 認証方式: SAML

### KeyCloak側のマッピング

| Type | Prop/Attr | SAML Attr  |
| ---- | --------- | ---------- |
| Prop | id        | id         |
| Prop | email     | email      |
| Prop | username  | username   |
| Prop | firstName | firstName  |
| Prop | lastName  | lastName   |

### GROWI側のマッピング

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/33914ef2-5034-8bd3-e0bd-4e5818c84bfe.png)

### 結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/6380a9c1-a2fd-dd4d-1e3a-1442ea0c44c6.png)

| DB Field | SAML Attr          | 変更  | 備考   |
| -------- | ------------------ | :---: | ------ |
| name     | firstName lastName |  可   | 表示名 |
| email    | email              |  可   |        |

- 表示名のフォーマットが `<firstName><半角スペース><lastName>` で固定されている
- firstNameやlastNameの値を空にすると `undefined` という文字列になって登録される
    - 例えば `nickname [空文字列]` にすると `ニックネーム undefined` になってしまう
- つまり**表示名に単一の値を割り当てることができない**
    - 片方を `<remove this>` とかにしてユーザーに任せるくらいしか方法がない
- idの用途は不明

<details><summary>MongoDBに登録されたユーザー情報</summary>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/e4bbcbdc-6d5a-1b8a-ad82-4f6d41e238e1.png)
</details>

## Mattermost

- バージョン: 5.32.1 (Docker)
- 認証方式: OAuth2 (GitLab互換)

### Keycloak側のマッピング

| Type | Prop/Attr | SAML Attr  |
| ---- | --------- | ---------- |
| Attr | longId    | id         |
| Prop | email     | email      |
| Prop | username  | username   |
| Prop | nickname  | name       |

longIdについては `Keycloak + Mattermost TE` のネタ元参照。今回は `12345` が入っている。

https://qiita.com/wadahiro/items/8b118c34aae904353865

### Mattermost側のマッピング

なし

### 結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/8263dd43-6ad5-ad56-11e8-67fdbd2f5450.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/f5f52037-79cd-f05b-edb1-32ea5de63860.png)

| DB Field  | SAML Attr | 変更  | 備考               |
| --------- | --------- | :---: | ------------------ |
| authdata  | id        | 不可  | 不可視, 識別に使用 |
| email     | email     | 不可  |                    |
| username  | username  | 不可  |                    |
| firstname | name      | 不可  | 表示名             |

- mention等に使われるusernameと表示名は分離しており、それぞれマッピングで個別に紐付けられる
- nameはfirstnameに投入され表示名になる
- Mattermost上では**ユーザー情報は変更できず、Keycloak側で変更すると次の認証の際に同期される**
- authdataに入るのはGitLabのユーザーID（gitlab.users.id: ユニークな整数）で、Mattermost側の認証に使用されているため重複や変更は不可

## rocket.chat

- バージョン: 3.12.1 (Docker)
- 認証方式: SAML

### Keycloak側のマッピング

| Type | Prop/Attr  | SAML Attr  |
| ---- | ---------- | ---------- |
| Prop | id         | id         |
| Prop | email      | email      |
| Prop | username   | username   |
| Attr | nickname   | nickname   |

### rocket.chat側のマッピング

rocket.chat管理画面の「SAML → マッピング → ユーザーデータのフィールドマップ」

```json
{
  "email": "email",
  "username": "username",
  "name": "nickname",
  "__identifier__": "id"
}
```

### 結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/45c40c66-b47e-0c89-0c39-46cf331142c1.png)

| DB Field | SAML Attr | 変更  | 備考               |
| -------- | --------- | :---: | ------------------ |
| id       | id        | 不可  | 不可視, 識別に使用 |
| username | username  |  可   | 表示名に設定可     |
| name     | nickname  |  可   | 表示名に設定可     |
| email    | email     |  可   |                    |

- rocket.chatの管理画面から「レイアウト → ユーザーインターフェース → 実名を使用する」でチャット欄の表示名をusernameからnameに切り替えられる
- 設定画面の説明にある通り、**姓名など任意のSAML Attrを組み合わせた上でフィールドに入れられる**
- 優秀

<details><summary>MongoDBに登録されたユーザー情報</summary>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/140b2b82-4daa-7d2a-491b-6d0264052022.png)
</details>

## Wekan

- バージョン: 5.05.0 (Docker)
- 認証方式: OpenID Connect

### Keycloak側のマッピング

| Type | Prop/Attr  | Claim Attr  |
| ---- | ---------- | ----------- |
| Prop | id         | id          |
| Prop | email      | email       |
| Prop | username   | username    |
| Attr | nickname   | nickname    |

### Wekan側のマッピング

```yaml
︙
- OAUTH2_ID_MAP=id
- OAUTH2_USERNAME_MAP=username
- OAUTH2_FULLNAME_MAP=nickname
- OAUTH2_EMAIL_MAP=email
︙
```

### 結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/6e8be8c2-5969-97b3-8695-9cd0781c0cef.png)

| DB Field          | Claim Attr |  変更  | 備考       |
| ----------------- | ---------- | :----: | ---------- |
| services.oidc.id  | id         |  不可  | 識別用     |
| username          | username   | 設定可 |            |
| profile.initial   | nickname   |   可   | 先頭1文字? |
| profile.fullname  | nickname   |   可   | 表示名     |
| emails[0].address | email      | 設定可 |            |

- イニシャルはデフォルトアバターに表示される文字列として使われる
- SAMLにも対応予定（ないし仮対応済）の模様

<details><summary>MongoDBに登録されたユーザー情報</summary>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/e7681ac8-37f3-1eb3-548b-bda4304699a8.png)
</details>

## TAIGA

- バージョン: 6.0.7 (Docker)
- 認証方式: OAuth2 (GitLab互換)
- 現状では `taiga-contrib-gitlab-auth` の実装に問題がありKeycloakと連携するにはソースの修正が必要

### Keycloak側のマッピング

| Type | Prop/Attr  | Claim Attr  |
| ---- | ---------- | ----------- |
| Prop | id         | id          |
| Prop | email      | email       |
| Prop | username   | username    |
| Attr | nickname   | name        |

### TAIGA側のマッピング

なし

### 結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/99340b27-1b47-f7f1-041b-a2aed100daa4.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/b625f518-c06e-d6a4-14d7-4feb9213b279.png)

| DB Field  | Claim Attr | 変更  | 備考            |
| --------- | ---------- | :---: | --------------- |
| username  | username   |  可   | mention等に使用 |
| full_name | name       |  可   | 表示名          |
| email     | email      |  可   |                 |

- mention等に使われるusernameと表示名は分離している
- 表示名にはOAuthの標準クレームの `name` が使用される（マッパーより優先される）
    - Keycloakのユーザー情報の「姓」「名」から生成される、片方しか設定されていない場合は片方だけになる
- Keycloakのユーザー情報に「姓」も「名」も存在しない場合はマッパーでクレーム名 `name` に紐付けた値が使用される
- idの用途は不明

## PENPOT

- バージョン: 1.3.0-alpha-1 (Docker)
- 認証方式: OAuth2 (GitLab互換)

### Keycloak側のマッピング

| Type | Prop/Attr | Claim Attr  |
| ---- | --------- | ----------- |
| Prop | id        | id          |
| Prop | email     | email       |
| Prop | username  | username    |
| Prop | nickname  | name        |
| Prop | nickname  | fullname    |

### PENPOT側のマッピング

なし

### 結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/5d76268b-1e10-67b7-dba6-88611a0a89a5.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/61533404-167e-d039-7881-91c152ea5280.png)

| DB Field | Claim Attr | 変更  | 備考   |
| -------- | ---------- | :---: | ------ |
| fullname | name       |  可   | 表示名 |
| email    | email      | 不可  |        |

- Claimに `name` と `fullname` が両方存在している場合のみ `name` が表示名として使われる
    - `name` だけだったりなにも紐付けないと表示名は `<firstName><半角スペース><lastName>` になる
    - GitLabのOAuth2プロバイダーが `fullname` を持っているため、その辺の処理の都合っぽい
- **識別には `email` が使用されており、Keycloak側で変更すると別アカウント扱いになってしまう**
- `id` は使われていない気がするし `username` も行方不明、よくわからない
- まぁまだAlpha版だし……

## NextCloud

- バージョン: 12.0.0.13 (Docker)
- 認証方式: SAML

### Keycloak側のマッピング

| Type | Prop/Attr  | SAML Attr  |
| ---- | ---------- | ---------- |
| Prop | email      | email      |
| Prop | username   | username   |
| Attr | nickname   | nickname   |

### NextCloud側のマッピング

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/fd96b3dd-6f3e-77c7-8240-335003f9d6b7.png)

### 結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/a195a1fb-0f2d-060b-172d-e6fd8577bced.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63934/2c0f65e7-97d0-0da4-32ad-ba0074ec572f.png)

| DB Field                       | SAML Attr | 変更  | 備考   |
| ------------------------------ | --------- | :---: | ------ |
| oc_user_saml_users.uid         | username  | 不可  |        |
| oc_user_saml_users.displayname | nickname  | 不可  | 表示名 |
| ?                              | email     | 不可  |        |

- 表示名に任意のプロパティ/属性を紐付けられる
- NextCloud上では**ユーザー情報は変更できず、Keycloak側で変更すると次の認証の際に同期される**