IBM のパブリック PaaS サービスである Bluemix を利用する上では、まず IBM ID ※と呼ばれるアカウントを取得する必要があります。そして IBM ID とパスワードでサービスにログインすることで、各種インスタンスを生成したり、設定変更したり、・・・というオペレーションが可能になります。

※正確には「Bluemix 用の IBM ID」と言うべきだと思ってます。


この IBM ID を自分のアプリケーションの認証として使うことができれば、わざわざユーザーディレクトリを別途用意する必要がなく、アプリケーションを「IBM ID でログインして使う」ということが可能になります。またこれができれば、例えば2つの異なるアプリケーションで共通のユーザーディレクトリを使いたい時にも、2つのユーザーディレクトリを用意する必要がなく、IBM IDという共通のアカウントを使えば(そしてアプリケーションの認証を IBM ID を使って実装すれば)できちゃうことになります。PaaS の環境にこのようなユーザーディレクトリまで用意されているのは便利ですね。 というわけで、今回は Bluemix 環境で SSO サービスを使ったアプリケーションを開発する方法の紹介をします。

まず(普通に)IBM IDで IBM Bluemix 環境にログインし、アプリケーションを1つ作成します(このアプリケーションで SSO サービスを使うことになります)。ここでは bluemixsso.mybluemix.net というホスト名でアプリケーションサーバーを作成しています。
2014071401a


そしてこのアプリケーションに SSO サービスをバインドします。ここまではデータベースサービスをバインドするような手順と同じです。
2014071401b

Single Sign On サービスを選択して、作成したアプリケーションにバインドします:
2014071401c


次に SSO サービスを REST API で使うためのエンドポイントを確認します。アプリケーションのページを開いて、その内の Single Sign On サービスパーツの "Show Credentials" をクリックします。
2014071403


このようなテキストフィールドが開いて、JSON テキストが表示されます:
2014071404


ここで表示される JSON テキストは以下のようになっています。この赤字部分(authorize_url, token_url, profile_resource の3つの値)を後で使うのでメモしておきます:
{
  "single.sign.on" : [ {
    "name" : "Single Sign On -5q",
    "label" : "single.sign.on",
    "plan" : "beta",
    "credentials" : {
      "profile_resource" : "https://idaas.ng.bluemix.net/idaas/resources/profile.jsp",
      "tokeninfo_resource" : "https://idaas.ng.bluemix.net/idaas/resources/tokeninfo.jsp",
      "openidProviderURL" : "https://idaas.ng.bluemix.net/idaas/openid",
      "token_url" : "https://idaas.ng.bluemix.net/sps/oauth20sp/oauth20/token",
      "authorize_url" : "https://idaas.ng.bluemix.net/sps/oauth20sp/oauth20/authorize"
    }
  } ]
}

次に SSO の Credential 情報を設定します。同じ画面の左ペインから "SERVICES" - "Single Sign On" をクリックします。画面右に現在の(初期状態の)SSO の設定内容が編集可能な状態で表示されます:
2014071405


以下のように編集して、最後に "Save" ボタンをクリックします:
 Identity Providers: "IBM" にチェック
 Authentication Protocol: "OAuth 2.0" を選択
 Display Name: ユーザーの画面に表示されるアプリケーション名称を指定
 Enabled: チェック
 Redirect URI: アプリケーションの OAuth リダイレクトURI (後述)を指定
2014071406


認証方法には OpenID か OAuth2.0 かを選ぶことができますが、今回は OAuth2.0 にしています。またその Provider に "IBM" を選ぶことで IBM ID を使った OAuth を指定していることになります。

入力内容に不備がなければ保存され、画面には "Client Identifier" と "Client Secret" という2つの値が表示されるはずです。この2つの値も後で(OAuth の認証時に)使うのでメモしておきます。
2014071407


ここまでの作業でサービス側の準備はできました。では次にこの SSO を実際に使って OAuth でログインするアプリケーションの実装方法を紹介します。なお、以下の例では Java および JavaScript を使って実装していますが、API そのものは REST を使うので他の言語でも同様の処理を記述すれば実装できると思います。

なお、今回はログイン後のページの URL(上記のRedirect URI) を http://localhost:8080/BluemixSSO/index.jsp として SSO で設定しています。あくまで localhost を使った開発環境での設定であり、本番稼働時にはここをhttp://bluemixsso.mybluemix.net/index.jsp という値に書き換える必要があります。ただ今回の紹介ではあくまで開発環境上だけでの稼働確認を行うためこの値にしています。適宜変更して利用してください。


まずはログイン前のページを用意します。ログイン後のページと同じページにしてもいいのですが、いろいろ面倒なのでログイン前は login.html、ログイン後は index.jsp というページが表示されるものとします。

で、ログイン前のページはこのような感じにします。このサンプルでは単純にログインボタンがあるだけです:
<html>
<head>
<title>ログイン前</title>
<script type="text/javascript">
var client_identifier = "(Client Identifierの値)";
//var client_secret = "(Client Secretの値)";
var server_url = "http://localhost:8080/BluemixSSO/index.jsp";
function login(){
  // authorize_url へリクエスト
  location.href = "https://idaas.ng.bluemix.net/sps/oauth20sp/oauth20/authorize?client_id=" + client_identifier + "&redirect_uri=" + server_url + "&scope=profile&response_type=code";
}
</script>
</head>
<body>
<input type="button" value="ログイン" onClick="login();"/>
</body>
</html>

このページ内のボタンをクリックすると JavaScript が実行されて、authorize_url に対してパラメータを付けてリクエストが実行されます。成功すると Redirect URL で指定した URL(http://localhost:8080/BluemixSSO/index.jsp) にアクセスコードが付与された形でリダイレクトされます。

そしてリダイレクト先(index.jsp)では以下の様なコードを用意しておきます:
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<@ page import="javax.servlet.http.*" %>
<@ page import="java.util.*" %>
<@ page import="java.io.*" %>
<@ page import="java.net.*" %>
<@ page import="java.util.*" %>
<@ page import="org.apache.commons.httpclient.*" %>
<@ page import="org.apache.commons.httpclient.methods.*" %>

<% request.setCharacterEncoding("utf-8"); %>

<html>
<head>
<title>ログイン後</title>
<%
String client_identifier = "aDUvXmXYEFSGtnNgX7v1";
String client_secret = "6w3YoxVpdoZ0mp2Q41IA";
String server_url = "http://localhost:8080/BluemixSSO/index.jsp";

String code = request.getParameter( "code" );
String access_token = null;
String email = "";
if( code != null && code.length() > 0 ){
  //. アクセストークンを取得
  String req_url = "https://idaas.ng.bluemix.net/sps/oauth20sp/oauth20/token?client_id=" + client_identifier + "&client_secret=" + client_secret;
  String param = "grant_type=authorization_code"
      + "&redirect_uri=" + URLEncoder.encode( server_url )
      + "&code=" + code;
  try{
    HttpClient client = new HttpClient();
    PostMethod post = new PostMethod( req_url );
		
    post.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded" );
    post.setRequestHeader( "Content-Length", "" + param.length() );
    post.setRequestBody( param );
		
    int sc = client.executeMethod( post );
    if( sc == 200 ){
      String body = post.getResponseBodyAsString();
      int n1 = body.indexOf( "\"access_token\":\"" );
      if( n1 > 0 ){
        int n2 = body.indexOf( "\"", n1 + 16 );
        if( n2 > n1 ){
          access_token = body.substring( n1 + 16, n2 );
					
          //. アクセストークンを使ってプロファイルデータを取得
          String req_url1 = "https://idaas.ng.bluemix.net/idaas/resources/profile.jsp";
          String param1 = "access_token=" + access_token;

          HttpClient client1 = new HttpClient();
          PostMethod post1 = new PostMethod( req_url1 );
						
          post1.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded" );
          post1.setRequestHeader( "Content-Length", "" + param1.length() );
          post1.setRequestBody( param1 );
          int sc1 = client.executeMethod( post1 );
          if( sc1 == 200 ){
            String body1 = post1.getResponseBodyAsString();
            n1 = body1.indexOf( "\"email\":[\"" );
            if( n1 > 0 ){
              n2 = body1.indexOf( "\"", n1 + 10 );
              if( n2 > n1 ){
                email = body1.substring( n1 + 10, n2 );
              }
            }
          }
        }
      }
    }
  }catch( Exception e ){
    e.printStackTrace();
  }
}else{
	
}
%>
</head>
<body> 
<h1>ようこそ、<%= email %> さん!</h1>
</body>
</html>

index.jsp 内では2回 HTTP リクエストを発行しています。
まず1回目のリクエストでは code パラメータの値を受け取り、その値を指定してアクセストークンを取得しています。
次に2回目のリクエストでは取得したアクセストークンを利用して、ログインユーザーのプロファイルデータにアクセスし、その email アドレスを取り出しています。 ここまでの処理が成功していれば、HTML 部分の <h1> タグ内にそのメールアドレスの値が出力される、というものです。

この辺りの情報について、詳しくは SSO のドキュメントも参考にしてください:
Getting started with Single Sign On (BETA)


実際にこのようなアプリを作って動かしてみると、以下のようになります:

まずログイン前の login.html ページにアクセスします。これは静的なコンテンツなので誰がアクセスしても同じ「ログイン」ボタンだけが表示される画面になるはずです:
2014071408

この「ログイン」ボタンをクリックすると JavaScript が実行されてリダイレクト処理が行われます。その処理の中で IBM ID によるログインが行われます:
2014071409

もし未ログイン状態であれば IBM ID とパスワードを指定していログインしてください:
2014071410

このアプリを最初に使う場合のみ、アクセスコードの入力が求められます。以下の様な画面が表示されると同時に、IBM ID のメールアドレスにメールが送信されます。この例では "4232-" と書かれたテキストフィールドが表示されていますが、送信メールには 4232- で始まる数字4桁-数字6桁 の文字列が書かれているはずです。
2014071411

受け取ったメールの内容の数字6桁部分を書き足して SUBMIT ボタンをクリックします:
2014071412

内容が正しければ先へ進みます。このような処理を毎回繰り返したくない場合はこのブラウザを登録することで回避出来ます。その場合はこの画面で "Register this Device" にチェックを入れ、その下のテキストフィールドにデバイス名を入力(半角スペースは使えません)して「送信」ボタンをクリックします:
2014071413

そして最後に OAuth 認証時にお馴染みに「このアプリに権限を与えるか?」の確認画面が表示されます。チェックボックスにチェックを入れて "Approve" ボタンをクリックします:
2014071414

で、無事に権限の譲渡が行われ、アクセストークンを使ってプロフィールの取得が行われ、(この例では)メールアドレスを取得して画面に表示する、という処理を行うことができました。
2014071415


今回紹介した例はかなりシンプルで、単にログイン前とログイン後のページを分けて、ログイン後のページでログイン情報を表示する、というだけの処理を実装しています。 実際にはログイン前後のページを同じにしたいこともあるでしょうし、またログイン後にログイン情報を残したまま別のページに移動したい、ということもあるでしょう。それらの場合はセッション情報などを使ってステータス管理をしながら処理を記述していくことになると思います。その辺りは IBM Bluemix の SSO サービスに特化した話ではないので、ここでは割愛します。

とはいえ、この例でも分かるようにサードパーティでも IBM Bluemix 上で IBM ID によるログイン管理を行うアプリの開発が出来ることがわかりました。Cloud Foundry をはじめとする各種 PaaS 環境でも共有ユーザーディレクトリが提供されているのは珍しいので便利に使えそうです。

しかも、IBM Bluemix 環境での SSO サービスの利用は(少なくとも 2014/07/14 の時点では)無料だったりします:
2014071401
※IBM Bluemix の各種サービス価格の最新情報はこちらを参照してください:
 IBM Bluemix: Pricing Sheet


どこかのタイミングで有料化される可能性がないとは言えませんが、PaaS 環境で共有で使えるユーザーディレクトリのサービス自体が珍しく、嬉しいです。