以前、OpenAMを使ってSAML2.0でSSOをする記事を書きましたが、SAML2.0を使ったSSOだとWebアプリケーション側が対応してないケースが多く、実装も困難であるため任意のWebアプリケーションに対するSSOの手段としてあまり適用できません。
ということで、今回から「OpenAMを認証プロバイダにして任意のWebアプリに対してシングルサインオン」をする方法に関して書いてみます!
SAML以外の方法で任意のWebアプリに対してOpenAMを使ってSSOするには以下のパターンがあります。
- Policy Agent
- OpenIG+PolicyAgent
- OpenID Connect
今回はPolicy Agentを使う方法でやってみます!
この方法はSSO対象のWebアプリケーションをホスティングしているサーバに、Policy Agent(リクエストを監視するソフトウェア)をインストールしてHTTPのアクセスを監視。認証していなければOpenAMへの認証要求(リダイレクト)をする仕組みになっています。
フローは以下のようになります。
■同一ドメインの場合(サブドメインが異なる場合も同じ)のフロー
Webアプリにアクセス
→WebアプリへのアクセスをPolicy AgentがインタラプトしてCookieが無効であればOpenAMにリダイレクト
→OpenAM側で認証(OpenAM側でセッション+Cookie発行)してWebアプリケーションにリダイレクト
→Webアプリへアクセス(Cookieが発行されているためAgentが認証済みとして処理)
■別ドメインの場合のフロー
Webアプリにアクセス
→WebアプリへのアクセスをPolicy AgentがインタラプトしてCookieが入っていなければOpenAMにリダイレクト
→OpenAM側で認証(OpenAM側でセッション+Cookie発行)してWebアプリにリダイレクト
→リダイレクト先でCookieを発行(cdcservlet)
→Webアプリへのアクセス(Cookieが発行されているためAgentが認証済みとして処理)
参考URLは以下のとおり。参考URL見た方がわかりやすい説。
- OpenAMによるシングルサインオン(1)エージェント編 - Tech-Sketch
- OpenAM Web Policy Agent Installation Guide
- Chapter 6. Configuring Policy Agent Profiles
1. OpenAM側の設定
まずトップ画面からアクセス制御を選択
対象のレルムを選択
レルムのトップ画面からエージェントを選択
新規のエージェントを作成
名前とパスワードは適当に(2で使います) サーバーURLはOpenAMのURLを入力。 エージェントURLはWebサーバーのURLを入力。
作成した後に詳細な設定ができます。
適用されないURLは認証を必要としないURLを入力。 適用されないURLの反転の有効にチェックを付けると上記設定が「適用されるURL」、つまり「認証を必要とするURL」に変更されます。
SSO対象のドメインが異なる場合(サブドメインが異なるのはOK)は、Cookieドメインリストに対象のドメインを入力します。クロスドメインのSSOでこれを設定しないと挙動がおかしくなります。(私の場合は認証無限ループしました)
認証した証拠となるCookieの名前は以下で設定します。デフォルトはiPlanetDirectoryProです。
2. WebアプリのサーバにPolicy Agentをインストール
今回はApacheのPolicy Agentを使います。OSはUbuntuで。
インストールガイド→http://docs.forgerock.org/en/openam-pa/3.1.0-Xpress/agent-install-guide/
まずはapacheインストール
$ sudo apt-get install apache2
インストールとともにサービスがstartしていたら
$ sudo service apache2 stop
でstopします。
$ apache2 -v
でバージョンを調べてバージョンに合ったPolicy Agentを以下サイトからダウンロード。
http://forgerock.org/downloads/openam-builds/
インストール実行前に以下のコマンドで空のhttpd.confファイルを作成(理由は後述)
$ sudo touch /etc/apache2/httpd.conf
agent用のパスワードファイルも作成
$ echo {1で設定したAgentのパスワード} > ~/.passwd
$ chmod 400 ~/.passwd
zip展開してbinディレクトリで以下を実行して対話型の設定を進める
$ agentadmin --install
設定例は以下のとおり。
apache2の設定フォルダ:/etc/apache2
OpenAMのURL:http://test.example.com:8080/openam
Webアプリ(SSO対象)のURL:http://hoge.fuga.com:80
エージェントの設定の名前(OpenAMで設定したエージェントの名前):ApacheAgent
パスワードファイル:/home/hoge/.passwd
インストール時にWebサーバの設定ディレクトリを聞かれますが、設定ディレクトリというよりもhttpd.confが含まれるディレクトリを指定してください。 どうやら指定した設定ディレクトリ内にあるhttpd.confにApache Agentの設定が追記される仕様なので、CentOSとかだとhttpd.confがあるのでそのまま設定ディレクトリを指定してあげればOKですが、Ubuntuとかだとapache2.confがメインの設定ファイルなのでhttpd.confを意図的に作成しないといけません。 ちなみにhttpd.confが無いディレクトリを指定すると「Invalid Apache Server Config directory」って言われます。
ダミーのhttpd.confの最後の行に
include /home/hoge/web_agents/apache24_agent/Agent_001/config/dsame.conf
が書き込まれているので、これを本物の設定ファイルapache2.confの末尾にコピーします。
で、stopしていたapacheをstart
$ sudo service apache2 start
3. テストアプリをWebアプリケーションとして設置。
OpenAMから発行されたCookieを元に認証を行うアプリを作成します。
今回はPython/Flaskで。
# -*- coding: utf-8 -*-
from flask import Flask, request, render_template
import urllib.request
import json
BASE_URL = "http://test.example.com:8080"
#値の検証とuidの取得
def validate(token):
req = urllib.request.Request(
url=BASE_URL + "/openam/json/sessions/" + token + "?_action=validate",
headers={"Content-Type" : "application/json"},
data="".encode()
)
res = urllib.request.urlopen(req)
return json.loads(res.read().decode())
app = Flask(__name__)
@app.route("/")
def index():
#Cookie値を取得
token = request.cookies.get("iPlanetDirectoryPro")
if token == None:
return "none"
#tokenの検証
validate_res = validate(token)
if not validate_res["valid"]:
return "invalid"
#ユーザ属性の取得
req = urllib.request.Request(
url=BASE_URL + "/openam/json/users/" + validate_res["uid"],
headers={"iplanetDirectoryPro" : token}
)
res = urllib.request.urlopen(req)
return res.read().decode()
app.debug = True
app.run()
OpenAMの設定で認証CookieキーをiPlanetDirectoryProとしているので、そのCookie値(token)を取得。あとは、そのtoken使ってOpenAMのREST APIを叩いてtokenの検証をした後にユーザ属性情報を取得します。
validateのレスポンスはこんな感じ。
{
"valid": true,
"uid": "amAdmin",
"realm": "/"
}
ユーザ属性はこんな感じ。
{
"username": "amAdmin",
"realm": "dc=openam,dc=forgerock,dc=org",
"mail": [],
"sunIdentityMSISDNNumber": [],
"sn": [
"amAdmin"
],
"givenName": [
"amAdmin"
],
"telephoneNumber": [],
"universalid": [
"id=amAdmin,ou=user,dc=openam,dc=forgerock,dc=org"
],
"employeeNumber": [],
"postalAddress": [],
"cn": [
"amAdmin"
],
"iplanet-am-user-success-url": [],
"roles": [],
"iplanet-am-user-failure-url": [],
"inetUserStatus": [
"Active"
],
"dn": [
"uid=amAdmin,ou=people,dc=openam,dc=forgerock,dc=org"
],
"iplanet-am-user-alias-list": []
}
あとはOpenAMのユーザ情報をWeb側のユーザ情報とマッピングしSession張るなりすればOK。
apache→flaskのリバースプロキシをしたいので、以下の一文をapache2.confに記述するのもお忘れなく。
ProxyPass / http://localhost:5000/
4. 動作確認
ネットワークレベルで動作確認をしてみます。以下はサブドメイン間のSSOをトラッキングしたものです。モザイクかけすぎて何が何だかわかんなくなっているのはご了承w
まずは何も認証してない状態でWebサイトにアクセスするとOpenAMへのリダイレクト処理が走ります。
次にOpenAM内でUI/login ->XUI/#loginへのリダイレクト処理が走ります。
そうするとログイン画面が出てくるので認証します。
ログインリクエストこんな感じ。
あとはJSでごにょごにょやってCookieセットして、OpenAM→Webサイトへリダイレクトします。この時点で有効なCookieが発行されているためWebサイト側で認証扱いになります。
ちなみに、クロスドメインSSOの場合はcdcservletというモジュールを介すため、ややシーケンスが増えます。
ハマりどころ
■作成したAgentをディレクトリ(例えばAgent_001)を削除してもう一度作成しようとすると
*** ERROR: Installation failed due to the following error - (Agent Agent_001 has already been configured for this Web Server instance.).
と言われてAgentがインストールできなくなります。
これは/etc/.amAgentLocatorにエージェントの設定ファイルの場所が書かれているためで、一度作成した設定を削除するにはこのファイルも削除しないとダメ。 というか、設定の削除は以下のコマンドでやるべきですね。
agentadmin --uninstall
■libamapc22.so: undefined symbol: ap_log_rerror
→undefined symbolと言われたらPolicy AgentとApacheのバージョンが合っているかを見直してください。
■権限系のエラー
→Webサーバの設定ディレクトリを指定するときには設定ファイルに対して書き込み権限が必要になりますし、パスワードファイルも読み込み権限が必要になります。この2つのファイルの権限を見直してください。
Policy Agentは設定もインストールもさほど難しくないので手軽では有りますが、OpenAMのCookieを使った認証になるため、プログラム側の改修が必要になります。つまり「認証されたらCookieにtokenを残す」「Cookieが存在すれば認証状態とする」ことはOpenAMとPolicy Agentが自動的に行ってくれますが、どのユーザでログインしているかどうかを判別する処理はアプリケーション側で実装しなければなりません。
ただ、Cookie認証の実装やOpenAMの設定がそんなに難しくないため、要件次第ではSSO実現の強力な方法になりそうです。