OpenIG with OpenAM環境の構築

# はじめに
#### 本記事は、[OpenAM/OpenIGを使用したシングルサインオン環境の構築](http://qiita.com/gzock/items/af6f820b5e872366e853)という記事から続く関連記事の一部です

本記事では、既にOpenIGが構築してあることを前提に、OpenAMとの連携を目指す。
これによりOpenAMにさえログインしていればその認証情報を使用して、OpenIGと連携しログイン代行処理を行うことができる。
結果、かなりそれっぽいSSO環境を実現できる。

OpenAMとOpenIGを連携させるには、OpenIG側(正確にはJettyやTomcat)にJ2EE Policy Agentをインストールする必要がある。
とはいえ、他記事で書いているWeb Policy Agentとやることはほぼ変わらないので大した手間にはならないと思う。
また、エージェントだけでなく、OpenAM/OpenIGどちらにも色々設定変更箇所がある。

流れとして、OpenIG設定 => OpenAM設定 => OpenIG設定 とちょっと面倒くさい流れになってしまうのだが・・・
OpenIGの公式ガイドとは少し違う流れなのだけど、こっちのほうがわかりやすいと思ったのでごめんなさい。

公式ガイドというのは、ForgerockのOpenIGのところから落とせる「openig-4-gateway-guide.pdf」であり、OpenAMとの連携はP51から始まる。
基本的に本記事は、P51からの内容を日本語にて説明し直してるものなので、英語得意な人は公式ガイド読んだほうが早いかもしれない。
ただ、公式ガイドは文字ばかりで、少しわかりづらい面もあるので、ここでは画像も交えながら説明を行う。

# 前提
* 既にOpenAMおよびOpenIGはインストール済であること
* 検証目的の構成を行う

# 環境
* OpenAM側
  * サーバOS: CentOS7.3
  * ホスト名: sso.test.local
  * IPアドレス: 172.16.1.130
  * JDK Ver: 1.8.0_111
  * Tomcat Ver: 8.0.39
  * OpenAM Ver: 13.0.0

* OpenIG側
  * サーバOS: CentOS7.3
  * ホスト名: openig.test.local
  * IPアドレス: 172.16.1.131
  * JDK Ver: 1.8.0_111
  * Jetty Ver: 8.1.21
  * OpenIG ver: 4.0.0

# 構築作業

## OpenIG側へのJ2EEエージェントインストール
[別記事](http://qiita.com/gzock/items/ac28015aaf1f9940c832)にまとめているので、参照して下さい。
インストールするJ2EE Policy AgentはJettyとTomcatで異なる。(設定内容は同じだが、Forgerockから落とすモノが違う)
また、バージョンも重要で、例えば2017年03月現在では、Jett v9には対応していない。

## OpenIG側設定-1

### 共通鍵生成
ログインユーザーのパスワードはDESで暗号化された状態でOpenAMからOpenIGに伝えられる。
その際の暗号化に必要なDES共通鍵をOpenIGから生成する必要がある。
まず鍵生成用のルートファイルを作成する。
鍵が不特定多数から生成されるのも困るので自分自身からのみアクセスできるようにし、DES共通鍵生成用のハンドラーに噛ませてあげる。

```
[jetty@openig ~]# vim .openig/config/routes/98-keygen.json

{
    "handler": {
    "type": "DesKeyGenHandler"
},
    "condition": "${matches(request.uri.path, '^/keygen')
        and (matches(contexts.client.remoteAddress, ':1')
        or matches(contexts.client.remoteAddress, '127.0.0.1'))}"
}
```

手っ取り早くcurlでアクセスしてみる。
今回の場合、`1U+YFlIcDjQ=`というのがDES共通鍵になる。
ちなみにこれは「openig-4-gateway-guide.pdf」に載っているやつ。

```
[jetty@openig ~]# curl http://localhost:8080/keygen
{"key":"1U+YFlIcDjQ="}
```

## OpenAM側設定
エージェントをインストールしただけでは実はOpenIGとの連携は完全には出来ない。
というのも、OpenIGはユーザーのパスワードを必要とするが、簡単にはOpenAMはパスワードを教えてくれない。
そのため、パスワードを暗号化された安全な状態でやり取りできるようにOpenAM/OpenIGどちらにも設定をする必要がある。
まずはOpenAM側の設定を。

### J2EE Policy Agent 設定変更
1. 作成したJ2EEエージェントの編集に入り、グローバル => 一般 => エージェントフィルタモード にデフォルトで`ALL`が設定されているはずなので、まずそれを削除。次に「対応するマップ値」に`SSO_ONLY`を入力し追加。設定後は必ず一番上の`保存`を行おう。

1. アプリケーションタブに移動し、セッション属性処理 => セッション属性フェッチモード を`HTTP_HEADER`に変更

1. 同項のセッション属性マッピングにて下記2つを設定する。こちらも設定後は必ず保存を。

* 1
    * マップキー: UserToken
    * 対応するマップ値: username
* 2
    * マップキー: sunIdentityUserPassword
    * 対応するマップ値: password

![2017-03-23-15-33-18.png](https://qiita-image-store.s3.amazonaws.com/0/80163/5c1a5d66-879d-1254-e533-83e7f1f6414c.png)



### 認証設定変更
1. トップページから`Top Level Realm` => `Authentication` => `Settings` => `ポスト認証プロセス` => `認証ポストプロセス` に`com.sun.identity.authentication.spi.ReplayPasswd`を入力し追加。設定後は`Save Changes`

![2017-03-23-13-55-20.png](https://qiita-image-store.s3.amazonaws.com/0/80163/996f93f8-0da7-6c45-d205-270e04b33a5c.png)


### 共通鍵設定
1. トップページから`Configuration` => `サーバーおよびサイト` => `http://sso.test.local:8080/openam (これは各自設定が違うと思うが・・・)` => `高度` に`追加`をクリックし、`com.sun.am.replaypasswd.key`に生成したDES共通鍵を指定し、保存

![2017-03-23-13-54-36.png](https://qiita-image-store.s3.amazonaws.com/0/80163/2b94b527-9927-28e6-076b-c57abe8555ca.png)

2. ここまで実施したらOpenAMの再起動を行う。OpenAMをTomcatで実行しているのであれば、systemctl restart tomcatとするか、検証環境なら手っ取り早くホストごと再起動させてしまうのも手。

## OpenIG側設定-2

### Jettyへのフィルター追加
OpenIGへのアクセス時、エージェントを経由するようにJettyへフィルターを追加する。
`Jettyインストールパス(例えば/opt/jetty/)/etc/webdefault.xml に下記を追加する。
※一番下の</webapp>の手前でok
これでHTTP GETなどの際にJ2EE Policy Agentが仕事をしてくれる。
むしろこの設定がないと、J2EE Policy Agentが仲介してくれないので、正しくOpenAMとの連携が行われない。
すっかりここの設定を忘れてしまうことがあるので、要注意。
一応、設定後はJetty自体を再起動しよう。(systemctl restart jetty)

```
<filter>
    <filter-name>Agent</filter-name>
    <display-name>Agent</display-name>
    <description>OpenAM Policy Agent Filter</description>
    <filter-class>com.sun.identity.agents.filter.AmAgentFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>Agent</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>ERROR</dispatcher>
</filter-mapping>
```

### ルートファイル修正

ここまでの作業により、OpenAMにログインしていない場合はOpenAMにリダイレクトされ、OpenIGが動的にユーザー名/パスワードを取得できるようになった。
現時点では、HTTP Headerにユーザー名/パスワードを保持している状態であるため、これらの情報を基にWebアプリケーションに対して動的に値を指定してPOSTできるようにルートファイルを修正する。
下記のようにroutesフォルダ配下の99-default.jsonを以下のように大幅変更する。

```
[jetty@openig ~]# vim .openig/config/routes/99-default.json

{
    "handler": {
        "type": "Chain",
        "config": {
            "filters": [
                {
                    "type": "PasswordReplayFilter",
                    "config": {
                        "loginPage": "${true}",
                        "headerDecryption": {
                            "algorithm": "DES/ECB/NoPadding",
                            "key": "DESKEY",
                            "keyType": "DES",
                            "charSet": "utf-8",
                            "headers": [
                                "password"
                            ]
                        },
                        "request": {
                            "method": "POST",
                            "uri": "http://openig.test.local:8081",
                            "form": {
                                "username": [
                                    "${request.headers['username'][0]}"
                                ],
                                "password": [
                                    "${request.headers['password'][0]}"
                                ]
                            }
                        }
                    }
                },
                {
                    "type": "HeaderFilter",
                    "config": {
                        "messageType": "REQUEST",
                        "remove": [
                            "password",
                            "username"
                        ]
                    }
                }
            ],
            "handler": "ClientHandler"
        }
    },
    "condition": "${matches(request.uri.path, '^/replay')}"
}
```

ただし、パスワードに関しては、OpenAMから渡ってきた状態ではDESによる暗号化状態にある。
Webアプリ側にPOSTするには平文としてのパスワード情報が欲しいので、複合処理を行う必要がある。
前項で生成したDES共通鍵を上記ルートファイルの`key`に指定する。本記事内の内容でいうなら、「1U+YFlIcDjQ=」をkeyに指定する。

ルートファイルの中身がさっぱり何が書かれているのかわからないと思う。
とても雑な説明をすると、`PasswordReplayFilter`は、暗号化されたパスワードの復号をし、対象とするログインページに対してPOSTを投げる動作をする。
ルートファイルを作成するときは基本的にPasswordReplayFilterを使用するパターンが多い。
この辺の詳しい説明は[別記事](http://qiita.com/gzock/items/0ffe8a73ac1ba12685a2)にまとめている。
一先ず詳しいことは抜きにして、こうすりゃ動く・・・と思って試してみてほしい。

# 動作確認
この状態で、http://openig.test.local/replay にアクセスする。OpenAMにログイン画面が表示されたら、`demo/changeit`でログインを行う。
そうすると、サンプルアプリケーションにリダイレクトされ、ログイン後画面が表示されるはず。
OpenAMのこのユーザー名/パスワードは、サンプルアプリケーションにおいても全く同じ文字列で存在しているため、SSOが成立する。
つまりOpenAM側で適当なユーザーアカウントを作成してそのアカウントでOpenAMにログインすると、恐らくサンプルアプリケーションでログインが失敗し、ログイン画面のままでストップすると思う。
これは単純明快で、サンプルアプリケーション側ではOpenAM側で作成したアカウントと同名/同パスワードのものが存在しないためである。

やったね！これで、OpenAM/OpenIGの基本的な構築と動作確認が完了しました。
OpenAMにログインしていれば、OpenIG側ではその認証情報を使って様々なことに応用できるようになったので、あとは、SSO対象のWebアプリ向けにルートファイルをどんどん作っていけば、晴れてハッピーSSO環境が構築できる。
とはいえ、このルートファイルの作り込みが非常に大変なのだが・・・
ルートファイルについては非常に長く複雑な話になるので、[別記事](http://qiita.com/gzock/items/0ffe8a73ac1ba12685a2)としてわけています。是非どうぞ。