目次


Liberty JAAS ログイン・モジュールを使用して Bluemix のシングル・サインオンに対応する

ログインと認証の処理を作成する負荷を軽減する強力な組み合わせ

Comments

Java 認証・承認サービス (JAAS) フレームワークは、ユーザーの認証および承認用の API を提供します。JAAS はアプリケーション・コードをユーザー認証・承認メカニズムから切り離すため、アプリケーション・コードを変更することなく、さまざまなログイン・モジュールを簡単に構成することができます。

従来の Liberty Web アプリケーションでの認証

従来の Liberty Web アプリケーションでの認証には、ユーザー資格情報を収集し、キャッシュ内の Subject をチェックし、JAAS サービスを呼び出して構成済みレジストリーに対して認証を行い、キャッシュ内に Subject がなければ作成するというプロセスが伴います。このプロセスでは、複数のリクエストにまたがって特定のユーザーを識別するために、HTTP Session オブジェクトも作成します。

Bluemix Liberty Web アプリケーションでの認証

IBM Bluemix クラウド・プラットフォーム上で Liberty for Java ランタイムによって実行される Web アプリケーションは、認証と承認に Single Sign On (SSO) サービスを利用します。認証は OAuth2 プロトコルによって処理されるため、この Web アプリケーションにはユーザー資格情報にアクセスする権限がありません。Bluemix Liberty Web 認証の場合、Liberty サーバーはこのプロセスを認識しないため、ユーザーを識別するために Subject や HTTP Session オブジェクトを生成しません。この場合、HTTP Servlet API (SessionPrincipal) が作成されないことから、アプリケーションがユーザーを識別する必要があります。さらに、コードでもベースとなるログイン・メカニズムを認識しなければなりません。Bluemix サービスは Bluemix UAA (User Account and Authentication Service) を使用して SSO を行いますが、そのためには Bluemix UAA の中に OAuth クライアント ID およびクライアント・シークレットが存在している必要があります。

このチュートリアルでは、アプリケーションに Liberty 対応の認証を組み込む方法を紹介します。サンプル・アプリケーションでは Bluemix SSO サービスをユーザー・ログインに利用する方法を明らかにします。この認証方法では、認証に成功すると、Liberty サーバーが必要なすべてのオブジェクト (SubjectPrincipalSession) を作成します。アプリケーション・コードはログイン・メカニズムから抽象化され、ユーザー識別を処理する負荷は Liberty に任されます。

Bluemix Liberty Web 認証の仕組み

カスタム JAAS ログイン・モジュールで Bluemix SSO サービスを利用して認証を行うとともに、カスタム資格情報 (ユーザー名、アクセス・トークン) を使用して Subject を作成します。Session オブジェクトはサーバーによって作成されます。ダウンストリームのコードからは、Subject API を使用して、カスタムの資格情報とプリンシパルにアクセスすることができます。このログイン・モジュールは、system.WEB_INBOUND 構成に含まれる Liberty のデフォルト・モジュールに置き換わるものです。このソリューションでは、以下のことが前提となります。

  • Bluemix SSO サービス・インスタンスが Liberty アプリケーションに追加されていること。
  • SSO クライアント構成データ (clientIDclientSecret など) を使用できること。

JAAS ログイン・モジュールは、SSO クライアント構成データを使用して構成されます。Liberty アプリケーションは、このログイン・モジュールおよび SSO サービスと通信するために、2 つの REST API (/dashboard および /sso_dashboard) を使用します。

以下のステップは、上の図に示されている番号に対応します。

  1. ユーザーが /dashboard API を呼び出して login プロセスを開始します。
  2. 呼び出された API は SSO サービスを利用して OAuth フローを開始するために、クライアント ID、状態 (クロスサイト・リクエスト・フォージェリーを防ぐため)、コールバック REST API を指定して、/authorize エンドポイント URL を呼び出します。
  3. OAuth フローが正常に完了すると、以下の処理が行われます。
  4. SSO サービスがコールバック API (/sso_dashboard) を呼び出し、一時認証コードと state 値を渡します。
  5. コールバック REST API は、カスタム JAAS ログイン・モジュールを呼び出して、認証コードを渡します。
  6. ログイン・モジュールが、アクセス・トークンを要求します。
  7. ログイン・モジュールは、SSO サービスからアクセス・トークンを取得します。
  8. ログイン・モジュールは、さらにユーザー名を取得します。
  9. ログイン・モジュールが、Principal と資格情報を使用して Subject を作成します。
  10. /sso_dashboard API (あるいは任意のダウンストリーム・コード) は、Subject API または Servlet API (HttpServlet.getUserPrincipal()) から情報にアクセスすることができます。このように、Liberty サーバーはこのログイン・プロセスを認識し、Subject キャッシュから認証済みユーザーを識別します。

アプリケーションを作成するために必要となるもの

アプリを実行するコードを入手する

サンプル・アプリケーションについて

この LoginTestApp という名前のサンプル Liberty アプリケーション (上掲の「コードを入手する」ボタンをクリックしてください) がカスタム・ログイン・モジュール (CustomLoginModule) を呼び出すと、このモジュールが、Bluemix SSO サービスを利用して JAAS 認証を行います。ログインに成功すると、アプリケーション・コードはベースとなる認証メカニズムを知らなくても、任意の認証関連の API (HttpServletRequest.getUserPrincipal() など) を呼び出せるようになります。

サンプル・アプリケーションを実行すると、HTML ページが開き、そこに「JAAS login with Bluemix SSO (Bluemix SSO による JAAS ログイン)」というリンクが表示されます。このリンクをクリックすることで、アプリケーションの /dashboard REST API が呼び出されます。すると認証プロセスが開始されて、Bluemix SSO ログイン・ページが表示されるので、このページに Bluemix 資格情報を入力します。認証に成功すると、Bluemix ID と Liberty セッション ID が表示されます。

ステップ 1. 単純な Bluemix Liberty JAX-RS アプリケーションを作成する

以下のサンプル・コードに示すように、2 つの REST API (/cloudLogin/dashboard および /cloudLogin/sso_redirect) を含めた簡単な Liberty JAX-RS アプリケーションを作成します。出発点となる /dashboard API が認証プロセスを開始します (ライン 44 ~ 45)。この API は OAuth クライアント ID、任意のストリング (state)、コールバック REST API (/sso_redirect) を指定して SSO サービス承認 API を呼び出します。

OAuth フローが正常に完了すると、Bluemix SSO サービスが /sso_redirect API を呼び出して、一時承認コードと任意のストリング (state) を渡します。すると、この API が HttpServletRequest.login(code, client_secret) メソッドを呼び出すことにより、Liberty カスタム・ログイン・モジュールを起動し、承認コードを渡します (ライン 63)。さらに getUserPrincipal() メソッドを呼び出して、ログインが成功したかどうかをチェックします。ログインに成功すると、Subject からユーザー情報 (名前) とログイン情報 (access-tokenSession) へのアクセスが行われます。ライン 85 では、複数のリクエストにまたがってユーザーを識別するために、Liberty サーバーが Session オブジェクトを作成しています。

43. @GET
44. @Path("/dashboard")
45. public Response dashboard() throws URISyntaxException {
46. 	System.out.println("Dashboard GET request headers: " + headersAsString(request));
47. 	String stateId = "anyUniqueString";
48. 	URI uri = new URI(AUTH_END_POINT + "?state=" + stateId 
49. 			+ "&scope=profile&response_type=code&client_id=" 
50. 			+ CLIENT_ID + "&redirect_uri=" 
51. 			+ buildRESTApiUrlString(SSO_END_POINT_PATH));
52. 	System.out.println("Dashboard GET redirect URI: " + uri);
53. 	return Response.seeOther(uri).build();
54. }
55. @GET
56. @Produces(MediaType.TEXT_HTML)
57. @Path(SSO_END_POINT)
58. public Response ssoDashboard(@QueryParam("code") String code, @QueryParam("state") String state) 
59. 		throws URISyntaxException, UnsupportedEncodingException, WSSecurityException {
60. 	System.out.println("ssoDashboard GET request headers: " + headersAsString(request));		
61. 	try {
62. 		// pass authorization code as user name to CustomeLoginModule
63. 		request.login(code, code);
64. 	} catch (ServletException e) {
65. 		e.printStackTrace();
66. 	}
67. 		if (request.getUserPrincipal() == null) {
68. 		// user is not logged in.
69. 		return Response.status(Status.UNAUTHORIZED).
70. 				type(MediaType.TEXT_HTML).
71. 				entity("You are not authorized.").build();
72. 	}
73. 
74. 	Subject subject = WSSubject.getCallerSubject();
75. 	Set<Object> credentials = subject.getPublicCredentials();
76. 	String userName = request.getUserPrincipal().getName();
77. 	String accessToken = null;
78. 	for (Object obj : credentials) {
79. 		if (obj instanceof HashMap) {
80. 			HashMap<String, String> cred = (HashMap) obj;
81. 			accessToken = cred.get("access-token");
82. 			// create application session with these information.
83. 		}
84. 	}
85. 	String libertySessionId = request.getSession().getId();
86. 	String msg = "<p>Welcome " + userName 
87. 			+ ". You are successfully authenticated using Bluemix SSO service.</p> " 
88. 			+ "<p> Your Liberty session ID :" + libertySessionId +"</p>";
89. 	return Response.status(Status.OK).type(MediaType.TEXT_HTML).entity(msg).build();

ステップ 2. Bluemix SSO ログイン・モジュールを実装する

このログイン・モジュールにオプションとして渡す必要があるものとしては、特定の URL (ユーザー・プロファイル・エンドポイント、トークン・エンド・ポイント、コールバック・ログイン REST API)、OAuth クライアント ID、OAuth クライアント・シークレットが挙げられます。これらの値は、クラス変数に格納されます。

44. public void initialize(Subject subject, CallbackHandler callbackHandler, 
       Map sharedState, Map options) {
45. 		this.subject = subject;
46. 		this.callbackHandler = callbackHandler;
47. 		this.sharedState = sharedState;
48. 
49. 		if (options.containsKey("PROFILE_END_POINT")) {
50. 			profileEndPoint = (String) options.get("PROFILE_END_POINT");
51. 		}
52. 
53. 		if (options.containsKey("CLIENT_SECRET")) {
54. 			clientSecret = (String) options.get("CLIENT_SECRET");
55. 		}
56. 
57. 		if (options.containsKey("SSO_END_POINT_PATH")) {
58. 			ssoEndPointPath = (String) options.get("SSO_END_POINT_PATH");
59. 		}
60. 
61. 		if (options.containsKey("CLIENT_ID")) {
62. 			clientId = (String) options.get("CLIENT_ID");
63. 		}
64. 		
65. 		if (options.containsKey("TOKEN_END_POINT")) {
66. 			tokenEndPoint = (String) options.get("TOKEN_END_POINT");
67. 		}
68. 	}

ログイン・メソッドは、Bluemix SSO サービスと通信して認証を行います。それにはまず、NameCallback オブジェクトを使用して一時認証コードを収集します (ライン 76 ~ 77)。

71. public boolean login() throws LoginException {
72. 		
73. 	System.out.println("Login module invoked");
74. 
75. 	// authorization code is passed as user name param value.  
76. 	NameCallback nameCallback = new NameCallback("User:");	
77. 	Callback callbacks[] = new Callback[1];
78. 	callbacks[0] = nameCallback;	
79. 
80. 	try {
81. 		callbackHandler.handle(callbacks);
82. 	} catch (IOException e1) {
83. 		e1.printStackTrace();
84. 	} catch (UnsupportedCallbackException e1) {
85. 		e1.printStackTrace();
86. 	}
87. 
88. 	// get the authorization code
89. 	String code = nameCallback.getName();
92.    CloudAccessToken accessToken = 
93. 		getAccessToken(clientId,
94. 				clientSecret,
95. 				tokenEndPoint,
96. 				code,
97. 				ssoEndPointPath);
98. 		
99. 	// get user name
100. 	HashMap<String, String> headerList = new HashMap<String, String>();
101. 	headerList.put("Authorization", accessToken.getFullAccessToken());
102. 	ASMRestClient restClient = new ASMRestClient();
103. 	ASMRestResponse response = restClient.doGet(profileEndPoint, headerList, null);
104. 	if (response.getStatusCode() != Status.OK.getStatusCode()) {
105. 		System.out.println("User profile end point returned status code: " 
106. 				+ response.getStatusCode());
107. 		return false;
108. 	}
109. 			
110. 	CloudUserInfo userInfo = response.getEntity(CloudUserInfo.class);
111. 	String name = userInfo.getUserDisplayName()[0];
112. 		
113. 	// add access token and user info as JAAS subject credentials.
114. 	HashMap<String, String> map = new HashMap<String, String>();		
115. 	map.put("access-token", accessToken.getAccessToken());
116. 	map.put("access-token-type", accessToken.getTokenType());		
117. 			
118. 	subject.getPublicCredentials().add(map);
119. 		
120. 	java.util.Hashtable<String, Object> customProperties = 
121. 			(java.util.Hashtable<String, Object>) sharedState.
122. 			get(AttributeNameConstants.WSCREDENTIAL_PROPERTIES_KEY);
123. 	customProperties = new java.util.Hashtable<String, Object>();
124. 		
125. 	customProperties.put(AttributeNameConstants.WSCREDENTIAL_UNIQUEID, name);
126. 	customProperties.put(AttributeNameConstants.WSCREDENTIAL_SECURITYNAME, name);
127. 	sharedState.put(AttributeNameConstants.WSCREDENTIAL_PROPERTIES_KEY,
128. 			customProperties);

上記のように JAAS の Subject が、Principal および資格情報とともに以下のように作成されます。

  • ライン 92 ~ 97: accessToken を取得するために、SSO サービス・トークン・エンドポイントに対し、clientIDclientSecret、承認コード、コールバック・ログイン REST API URL を指定した HTTP リクエストを行います。
  • ライン 102 ~ 111: 取得した accessToken をプロファイル・エンドポイント・リクエストの承認ヘッダーとして使用して、ユーザー名を取得します。
  • ライン 114 ~ 128: Bluemix SSO サービスから取得した認証済みユーザー情報を使用して、JAAS の Principal および資格情報を作成します。
  • ライン 114 ~ 118: アクセス・トークンとそのタイプを格納する Subject に、公開資格情報を追加します。
  • ライン 125 ~ 128: ユーザー情報を使用したハッシュ・テーブルを (初期化メソッドで渡された) 共有状態変数に追加して、JAAS の Principal を作成します。
  • ライン 127: Liberty フレームワークが、共有状態変数に格納された定義済みキーを使用してハッシュ・テーブル・オブジェクトの値を検索し、Principal を作成します。

ステップ 3. Bluemix SSO サービスを追加して SSO クライアント構成を生成する

次は、サンプル・アプリケーションを Bluemix にプッシュして、SSO サービスをこのアプリケーションにバインドします。「Single Sign On 入門」で説明しているステップに従って SSO クライアント構成を生成し、コールバック URI の値として /sso_redirect URL を指定してください。

clientID と client-secret の値を記録しておいてください。これらの値は、次のステップで必要になります。

ステップ 4. カスタム JAAS ログイン・モジュールを構成する

このカスタム・ログイン・モジュールは、サンプル Liberty アプリケーションの中で構成されます。以下に、このカスタム・ログイン・モジュールをロードする server.xml ファイルのスニペットを記載します。このログイン・モジュールのライブラリーはサーバーの lib フォルダーに置かれていて、customLoginLib 要素によって参照されます (ライン 25)。ライン 30 に示されているように、sytem.WEB_INBOUND 構成のデフォルト・ログイン・モジュールは、このカスタム・ログイン・モジュールとハッシュテーブル・モジュールで置き換えられます。ライン 33 ~ 40 に、カスタム・ログイン・モジュールの実装および期待されるオプション (cliendIDclient-secret、ユーザー・プロファイル URL、コールバック REST API URL、およびトークン・エンドポイント URL) をロードする方法が示されています。

24. 	
25. 	<library id="customLoginLib">
26. 		<fileset dir="${server.config.dir}/lib" includes="*.jar"/>
27. 	</library>
28. 	
29. 	
30.   	<jaasLoginContextEntry id="system.WEB_INBOUND" loginModuleRef="custom, hashtable" 
31. 	name="system.WEB_INBOUND"/>
32. 	
33. 	<jaasLoginModule className="login.module.CloudLoginModule" controlFlag="REQUIRED" 
34. 		id="custom" libraryRef="customLoginLib">
35.   		<options CLIENT_ID="${client.id}"
36.   			 CLIENT_SECRET="${client.secret}"
37.   			 PROFILE_END_POINT="${profile.end.point}" 
38.   			 SSO_END_POINT_PATH="${sso.end.point}"
39.   			 TOKEN_END_POINT="${token.end.point}"/>
40.   	</jaasLoginModule>

ステップ 5. アプリケーションをビルドして Bluemix にプッシュする

以下の手順に従って、サンプル・アプリケーションをビルドしてデプロイします。

  1. Bluemix Liberty アプリケーションを以下の手順で作成します。
    1. Bluemix にログインして、Liberty アプリケーションを作成します。
    2. 以下の cf コマンドを使用して、Bluemix SSO サービスを自分のスペースに追加します。
      cf create-service SingleSignOn standard sso_service
    3. 以下の cf コマンドを使用して、Bluemix SSO サービスをアプリケーションにバインドします。
      >cf bind-service app_name sso_service
    4. SSO クライアント構成を生成します。
      1. 以下のリダイレクト URL 値を指定します。
        https:<hostname>.mybluemix.net:443/api/cloudlogin/sso_redirect
      2. clientIdclient-secret を記録します。
  2. このプロジェクトを新規 DevOps Services プロジェクトにフォークします。
  3. サンプル・アプリケーションを以下の手順でダウンロードします。
    1. DevOps Services からこのサンプル・アプリケーションをダウンロードするための Eclipse IDE をセットアップします (「Setting up local Eclipse clients to work with Jazz source control」を参照)。
    2. セットアップした Eclipse IDE にサンプル・プロジェクトをインポートします。
    3. Liberty プロファイル・ランタイムをダウンロードします (「Download just the Liberty profile runtime」を参照)。
    4. アプリケーションを Bluemix にプッシュするために使用する cf コマンド・ライン・プログラムをダウンロードします (「コマンド・ライン・インターフェース」を参照)。
    5. Liberty プロファイル用の Eclipse プラグインをダウンロードして、構成します (「Download Liberty profile in Eclipse」を参照)。
    6. Eclipse サーバー・ビューからアプリケーションをデプロイするために使用する Liberty サーバーを作成します (例えば、Liberty\usr\servers\loginapp)。
  4. アプリケーションを以下の手順でビルドします。
    1. 以下の ant ビルド・スクリプトに、Liberty ランタイムの場所と Liberty サーバーの場所を指定します。
      • CustomLoginModule\build.xml
      • LoginTestApp\build.xml
          <property name="Liberty.location" value="C:\Liberty"/>
          <property name="server.name" value="loginapp"/>
    2. LoginTestApp\Config.xml ファイルに、SSO クライアント構成のステップで記録した OAuth clientId および OAuth client-secret を指定します。
      <server>
        <variable name='client.id' value='YOUR_CLIENT_ID'/>
        <variable name='client.secret' value='YOUR_CLIENT_SECRET'/>
    3. LoginTestApp/build.xml の ant ビルドを実行して、プロジェクトをビルドします。ビルドが完了すると、サーバー・フォルダー (例えば、Liberty\usr\servers\loginapp) が依存関係と構成によって更新されていることがわかります。
  5. アプリケーションを以下の手順で Bluemix にデプロイします。
    1. manifest.yml ファイルを更新して、アプリケーション名、ホスト名、Liberty サーバーのパス (例えば、C:\Liberty\usr\servers\loginapp) を反映させます。
    2. 以下の cf コマンドを使用して、アプリケーションを Bluemix にプッシュします。
      C:\Program Files (x86)\CloudFoundry>cf push -f c:\manifest.yml

デプロイが完了すると、アプリケーション (https://<hostname>.mybluemix.net/) にアクセス可能になります。

ステップ 6. サンプル・アプリケーションを実行してテストする

以下の手順に従って、このサンプル・アプリケーションをテストします。

  1. アプリケーション の URL (https://<hostname>.mybluemix.net/) にナビゲートします。「JAAS login with Bluemix SSO (Bluemix SSO による JAAS ログイン)」という Web リンクがあるページが開きます。
  2. ID の提供元 (IBM や Facebook など) によるログイン・ページが表示されるので、そこに資格情報を入力します。
  3. SSO サービスから、セキュア・アクセス・コードが e-メールで送られてきます。
  4. アクセ・コードを入力します。Bluemix SSO でのログインに成功すると、以下のようなメッセージが表示されます。
    You are successfully authenticated using Bluemix SSO, Session ID :g-IZDSgUdVZXfH8E3NAf4fw

まとめ

このチュートリアルでは、JAASベースの認証と Bluemix SSO サービスを使用することで、Bluemix Liberty アプリケーションにシングル・サインオン機能を組み込む方法を紹介しました。このサンプル・プログラムの構成を基に、さらなるメリットを追求してください。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=セキュリティ, Cloud computing, Java technology
ArticleID=1006826
ArticleTitle=Liberty JAAS ログイン・モジュールを使用して Bluemix のシングル・サインオンに対応する
publish-date=05282015