WebSphere＋OpenAMのSAML SSO環境構築（2/3）（SP構築編）

# WebSphereでのSAML SSOシナリオにおけるSPの構築
[前回](https://qiita.com/ch7821/items/b3311b8c6cfe1fb47b39)はIdPを構築しましたので、今回はSPを作ります。
実は全３回分を一気に書き上げて保存しておいたのを、あまりに長すぎるので三分割したんですが、残り３分の２を消してしまいました。。。心を痛めつつ、なるべく新鮮さを失わないように書いていきます。

SP環境構築
===========================================================================================

IHSのSSL有効化
------------------------------------

SAMLのSSOシナリオにおいては、やはり通信経路は暗号化（SSL）するのがグッドプラクティス（というかほぼMUST）ですので、IHSとの通信はHTTPSとします。鍵と証明書は作成済みなので、これをIHSに食わせてやります（IHSからWASの通信はIHSプラグインの設定で、リクエストがHTTPであればHTTP、HTTPSであればHTTPSになります。IHSでSSLをほどいて`ssl-accelerator`として動作させることも可能です）。ついでにクライアント認証できるように、CAの証明書も持たせてやります。前の手順で一度`PKCS12`キーストアを作っていますが、作り直します。

```bash
mac:$ openssl pkcs12 -inkey ~/openAM/work/ihs.key -name www.docker.mognet.net -caname mognetca \
      -in ihs.crt -export -out www.docker.mognet.net.p12 -CAfile ~/mognetCA/cacert.pem -chain
mac:$ cp www.docker.mognet.net.p12 /opt/IBM/HTTPServer/conf
```

`httpd.conf`を修正して、SSLを有効化します。

```diff:httpd.conf
849,855c849,860
< #LoadModule ibm_ssl_module modules/mod_ibm_ssl.so
< #Listen 443
< #SSLCheckCertificateExpiration 30
< #<VirtualHost *:443>
<  #SSLEnable
<  #Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
< #</VirtualHost>
---
> LoadModule ibm_ssl_module modules/mod_ibm_ssl.so
> Listen 443
> SSLCheckCertificateExpiration 30
> <VirtualHost *:443>
>  ServerName www.docker.mognet.net
>  SSLEnable
>  KeyFile /opt/IBM/HTTPServer/conf/www.docker.mognet.net.p12
>  SSLServerCert www.docker.mognet.net
>  SSLClientAuth Optional
>  SSLProtocolEnable TLSv10 TLSv11 TLSv12
>  Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
> </VirtualHost>
857c862
< #SSLDisable
---
> SSLDisable
```

コンテナにログインし、`PKCS12`キーストアのパスワードファイルを生成して、IHSを再起動します。

```bash
mac:$ docker exec -it ihs bash
root:# /opt/IBM/HTTPServer/bin/sslstash -c /opt/IBM/HTTPServer/conf/www.docker.mognet.net.sth crypto password
root:# /opt/IBM/HTTPServer/bin/apachectl restart
```

ブラウザを起動してHTTPS接続できることを確認します。ついでにWAS連携できていることも確認、、、の前に、IHSとWAS間の鍵交換がまだできていなかったので、WASのWebコンソールからHTTPSトランスポートチェーンの設定を完成させます。
「サーバー」ー「サーバー・タイプ」ー「Webサーバー」を開き、「webserver1」に入ります。![スクリーンショット 2018-01-25 21.14.50.png](https://qiita-image-store.s3.amazonaws.com/0/232587/6439bbe5-d673-76cb-ca51-053cf5a39c79.png)「プラグイン・プロパティー」に入ります。![スクリーンショット 2018-01-25 21.14.57.png](https://qiita-image-store.s3.amazonaws.com/0/232587/234e1a20-b730-c518-7e38-9b8958cab02a.png)「Webサーバー鍵ストア・ディレクトリーへコピー」を押します。![スクリーンショット 2018-01-25 21.15.02.png](https://qiita-image-store.s3.amazonaws.com/0/232587/9078b5da-4292-6949-da53-f490fbd4d9c9.png)「Webサーバー」に戻ります。![スクリーンショット 2018-01-25 21.15.09.png](https://qiita-image-store.s3.amazonaws.com/0/232587/62a53bcf-eecf-5f41-5e00-644541edd30a.png)「webserver1」にチェックして、「停止」→「開始」で再起動です。![スクリーンショット 2018-01-25 21.15.14.png](https://qiita-image-store.s3.amazonaws.com/0/232587/86150b3d-84c0-2807-fb27-6ad5c44a3fcd.png)
![スクリーンショット 2018-01-25 21.15.51.png](https://qiita-image-store.s3.amazonaws.com/0/232587/294408b8-9c84-2a9b-893a-5f212f2efb52.png)

無事に`snoop`サーブレットまでSSL通信できました。

SAML TAIの構成
------------------------------------
WASが提供する重要な機能であるTAI（Trust Association Interceptor）を構成します。`Interceptor`の名の通り、この機能はリクエストをインターセプトして、言い換えればSAMLアサーションを捕まえて、内容（署名や宛先など）を確認したうえで、`javax.security.auth.Subject`オブジェクトを作成してくれます。オブジェクトはサーブレットコンテクストから、`getCallerSubject()`メソッドで取得できます。SAML処理の重要というか要の部分を製品がやってくれるというわけですから、これは助かりますね。

Webコンソールから設定していきます。![スクリーンショット 2018-01-25 21.34.37.png](https://qiita-image-store.s3.amazonaws.com/0/232587/18d273bf-a424-bd45-ea5a-796aff05c6ea.png)「セキュリティー」ー「グローバル・セキュリティー」を開き、「アプリケーション・セキュリティーを使用可能にする」を有効にして「適用」します。![スクリーンショット 2018-01-25 21.40.54.png](https://qiita-image-store.s3.amazonaws.com/0/232587/0a59f0a3-71ea-4505-b169-88c67bbac341.png)同じく「グローバル・セキュリティー」から、「WebおよびSIPセキュリティー」を開き、「トラスト・アソシエーション」に入ります。![スクリーンショット 2018-01-25 21.34.57.png](https://qiita-image-store.s3.amazonaws.com/0/232587/f558c5e1-838f-3212-6781-dd5bfa0c04c3.png)「トラスト・アソシエーションを使用可能にする」を有効にして「適用」します。![スクリーンショット 2018-01-25 21.35.06.png](https://qiita-image-store.s3.amazonaws.com/0/232587/4d5eded5-5b5f-659f-9e1e-a63bb01c232d.png)再度「トラスト・アソシエーション」に入り、今度は「インターセプター」に入ります。![スクリーンショット 2018-01-25 21.35.26.png](https://qiita-image-store.s3.amazonaws.com/0/232587/3e3afdad-192d-55db-b65b-e4ff9130cbb2.png)「新規作成」を押します。![スクリーンショット 2018-01-25 21.35.31.png](https://qiita-image-store.s3.amazonaws.com/0/232587/a1b45c8b-949d-72d8-11ac-198acfbb79b7.png)

下表のとおり入力し、「適用」します。

| 入力項目 | 入力値 |
|:---:    |   :---:   |
|インターセプター・クラス名   |com.ibm.ws.security.web.saml.ACSTrustAssociationInterceptor|
|カスタム・プロパティー名     |sso_1.sp.acsUrl|
|カスタム・プロパティー値     |https://www.docker.mognet.net/samlsps/acs|
|カスタム・プロパティー名     |sso_1.sp.idMap|
|カスタム・プロパティー値     |idAssertion                                       |

![スクリーンショット 2018-01-25 21.37.00.png](https://qiita-image-store.s3.amazonaws.com/0/232587/25068cda-6006-4745-1cb1-3793061938ca.png)注目すべき点は、`acsUrl`の値です。ここにはIdP（ユーザ）から見たSPへのエントリポイントのURLが入ります。URLホスト部はWebサーバのホスト名になります。後ろの`/samlsps/acs`はWASが提供するサーブレットで、後でデプロイします。コンテクストルート`/samlsps`は固定（初期値）で、`/samlsps/*`が全て同じサーブレットにマッピングされてます。なので、`acs`の部分はどんな文字列でも構いません。このサーブレット自体は、単純にリクエストの中に設定された`RelayState`パラメタのURLにリダイレクトさせる機能を持っているだけです。`Subject`の生成などは全てTAIの機能です。

続けて、「グローバル・セキュリティー」の「カスタム・プロパティー」に入ります。![スクリーンショット 2018-01-25 21.37.23.png](https://qiita-image-store.s3.amazonaws.com/0/232587/cb47ab41-bdfa-d0fb-d83a-802a6cc503b0.png)定義済みの「com.ibm.websphere.security.DeferTAItoSSO」を開きます。![スクリーンショット 2018-01-25 21.37.31.png](https://qiita-image-store.s3.amazonaws.com/0/232587/d64a4b75-004c-cd8e-b48a-12c5be2ecfae.png)プロパティ値に`"com.ibm.ws.security.web.saml.ACSTrustAssociationInterceptor"`を入力して「OK」します。![スクリーンショット 2018-01-25 21.37.42.png](https://qiita-image-store.s3.amazonaws.com/0/232587/a4324b40-dcd9-694e-010f-9b61e563600e.png)さらに、カスタム・プロパティーを「新規作成」します。![スクリーンショット 2018-01-25 21.37.47.png](https://qiita-image-store.s3.amazonaws.com/0/232587/572ce7c6-697d-6824-c815-36a2ecb11f23.png)プロパティー名に`"com.ibm.websphere.security.InvokeTaibeforeSSO"`、プロパティー値に`"com.ibm.ws.security.web.saml.ACSTrustAssociationInterceptor"`をそれぞれ入力して「OK」します。

![スクリーンショット 2018-01-25 21.38.01.png](https://qiita-image-store.s3.amazonaws.com/0/232587/484249b8-5891-5a67-db98-b7c3c712dfdf.png)これらのプロパティはそれぞれ、SSO前後に起動（`Invoke`）するInterceptorクラスを指定します。ここでは、`SAML TAI`を使用するため、全て`com.ibm.ws.security.web.saml.ACSTrustAssociationInterceptor`を指定します。

さらに、「グローバル・セキュリティー」から「ユーザー・アカウント・リボジトリー」の「構成...」を押します。![スクリーンショット 2018-01-25 21.40.06.png](https://qiita-image-store.s3.amazonaws.com/0/232587/a85eac7b-e26e-1ec4-b562-2aec7f44e75d.png)「関連項目」の「トラステッド認証レルム・インバウンド」に入ります。![スクリーンショット 2018-01-25 21.40.15.png](https://qiita-image-store.s3.amazonaws.com/0/232587/3ab04fdb-01db-f288-e298-ed4cef3bfef2.png)「外部レルムの追加...」を押します。![スクリーンショット 2018-01-25 21.40.20.png](https://qiita-image-store.s3.amazonaws.com/0/232587/37e31362-09f7-a3ed-8fed-03e3e9e77dd2.png)レルム名にIdPのURL（正確には`EntityID`）である`"https://www.alto.net/openam"`を入力して「OK」します。![スクリーンショット 2018-01-25 21.40.39.png](https://qiita-image-store.s3.amazonaws.com/0/232587/70f3f192-225a-d9bc-d227-57e1c26d2a4d.png)構成を「保存」して、サーバーを再起動します。![スクリーンショット 2018-01-25 21.38.36.png](https://qiita-image-store.s3.amazonaws.com/0/232587/27d1841e-3d64-4976-1d9e-7cfaa82f4fea.png)

先ほどのTAIのカスタム・プロパティーの設定で、`sso_1.sp.idMap`の値を`idAssertion`にしました。他に設定可能な値は、`localRealm`です。`localRealm`を指定すると、JavaEEサーバでJDBCレルム等で管理しているユーザとSAMLアサーションのユーザ情報をマッピングし、管理しているユーザ属性を取ってきて`Subject`を生成してくれます。今回はユーザリポジトリをWASに持たせていないので、`idAssertion`を指定して、SAMLに乗ってきた属性をそのまま`Subject`にしてもらいます。なお、`localRealmThenidAssertion`という選択肢も取れて、これにするとまずはWASのリポジトリからユーザを検索し、いなかったらSAMLの属性をそのまま使うという動きになります。

改めて、ここではSAMLアサーションを発行した`EntityID`を信頼する（トラステッドにする）ということをしました。抽象的でわかりづらいですが、具体的に言えばSAMLアサーション中の、`EntityID`属性の値が`https://www.alto.net/openam`となっているものは認証済みとして信頼しますよ、ということです（もちろん、実際にはさらに署名の検証等もしますので、この設定だけをもって全てのトラステッドレルムからのSAMLアサーションを受け入れる訳ではありません）。

ACSアプリケーションのデプロイ
------------------------------------
続けて管理コンソールからWASが提供するACSアプリケーションをデプロイします。
「アプリケーション」ー「新規アプリケーション」を開き、「新規エンタープライズ・アプリケーション」に入ります。![スクリーンショット 2018-01-25 22.18.17.png](https://qiita-image-store.s3.amazonaws.com/0/232587/1924f1ab-c1a2-9bed-8714-2320e98a5a4b.png)「リモートファイルシステム」にチェックし、`/opt/IBM/WebSphere/AppServer/installableApps/WebSphereSamlSP.ear`を選択して「次へ」いきます。![スクリーンショット 2018-01-25 22.19.08.png](https://qiita-image-store.s3.amazonaws.com/0/232587/86db6a56-2f43-3afb-4e83-f21143961e34.png)デプロイ自体は「ファストパス」でも可能ですが、見ておきたい部分があるので、「詳細」を選んで「次へ」いきます。![スクリーンショット 2018-01-25 22.20.39.png](https://qiita-image-store.s3.amazonaws.com/0/232587/14fb93cb-11f3-c157-837c-5dd7120ec6a2.png)ほとんどデフォルトのまま「次へ」進んでいきますが、「モジュールのマッピング」だけはモジュールを選択した（チェックした）状態で、「クラスターおよびサーバー」からAPサーバとWebサーバー両方を選んで「適用」してから、「次へ」進みます。Webサーバーも含めることで、プラグインの生成と伝搬が自動で行われます。![スクリーンショット 2018-01-25 22.21.25.png](https://qiita-image-store.s3.amazonaws.com/0/232587/7af93644-bb27-160f-9a68-da9e72cae9a7.png)さらにデフォルトのまま進んでいくと、「ユーザーまたはグループへのセキュリティー・ロールのマップ」画面になります。![スクリーンショット 2018-01-25 22.22.17.png](https://qiita-image-store.s3.amazonaws.com/0/232587/1ed8d015-bdd6-da3a-8e3d-d8f5aa2151ea.png)これが非常に重要で、ACSアプリケーションの必須要件として、宣言型のセキュリティ制約をかけ、適当なロールを作り、特殊サブジェクトである「トラステッド・レルム内で認証済みすべて」がこのロールに割り当てられる必要があります。

これは言い換えれば、どのようなアプリケーション（サーブレット）でもセキュリティ制約をかけて特殊サブジェクトをロールに割り当てるという実装をしていれば、ACSとして振舞うことができることを意味します。WASが提供するACSサーブレットは、単純にリクエストをリダイレクトするだけなので、自作サーブレットが直接業務リクエストを受けられてSAML属性を取得できるのであれば、その方がクライアントとの通信回数が１回分減るので、若干効率的です。特にサービスのエントリーポイントが単一なのであれば、わざわざこのアプリケーションを使う必要はないと思います。複数ある場合、たとえばメインメニューに入る場合と、商品ページに行く場合等でも、同一URLで受けてリクエストをディスパッチするようなサーブレット実装にすれば、わざわざ一度クライアントにレスポンスを返す必要はなさそうです（そういう風に実装する場合もあるでしょうけれど）。特にアクセスの多いサイトでは、一往復分のやりとりを減らしたい、ということも多いかもしれませんので。

残りの項目もデフォルトのまま「次へ」いき、「終了」して「保存」します。![スクリーンショット 2018-01-25 22.22.48.png](https://qiita-image-store.s3.amazonaws.com/0/232587/9ba0879e-50e9-11e3-3468-e4d7bfbc6525.png)

これでSPとしての機能は実装できました。実際に提供するサービスとして、今回の検証ではサンプルアプリケーションの`snoop`を使います。
[次回](https://qiita.com/ch7821/items/c01d1fdb23bb5d8caec8)はIdPとSPとのメタデータを交換した上で、実際にSAMLによるシングルサインオンを実践してみます。

参考文献
===========================================================================================
* [IBM Knowledge Center](https://www.ibm.com/support/knowledgecenter/ja/SSAW57_8.5.5/com.ibm.websphere.nd.doc/ae/cwbs_samlssosummary.html)
* [WebSphere SAML構成ガイド](https://www.ibm.com/developerworks/jp/websphere/library/was/saml_sso_guide/index.html)
* [Understanding the SAML trust association interceptor for the WebSphere Application Server](https://www.ibm.com/developerworks/library/mw-1612-lansche-trs/index.html)