目次


Java プログラミングで実装する OAuth 2.0 クライアント

第 2 回 クライアント・クレデンシャル・グラント

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: Java プログラミングで実装する OAuth 2.0 クライアント

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:Java プログラミングで実装する OAuth 2.0 クライアント

このシリーズの続きに乞うご期待。

概要

OAuth は、保護されたサーバー・リソースへのアクセス権を、リソース・オーナーに代わってクライアントが取得できるように認可するためのオープン・スタンダードです。リソース・オーナーは、別のクライアントの場合もあれば、エンド・ユーザーの場合もあります。OAuth はまた、エンド・ユーザーが自ら所有するサーバー・リソースへのアクセスをサード・パーティーに対して認可する上で役に立ちますが、そのために自分のクレデンシャル (ユーザー名とパスワードなど) を共有する必要はありません。この記事のシリーズでは、RFC6749 で概説している OAuth 2.0 認可フレームワークに忠実に従います。RFC 6749 で概説している完全な OAuth 2.0 認可フレームワークについては、Internet Engineering Task Force の Web サイトを参照してください。

認可グラント

認可グラントとは、保護されたリソースにアクセスするために使用できる、リソース・オーナーの認可を表すクレデンシャルのことです。クライアントは、このクレデンシャルを使用してアクセス・トークンを取得し、そのアクセス・トークンを、保護されたリソースにアクセスするためのリクエストと一緒に送信します。OAuth 2.0 では、認可グラントのタイプとして、次の 4 つを定義しています。

  1. 認可コードによるグラント
  2. インプリシット (暗黙的) グラント
  3. リソース・オーナー・パスワード・クレデンシャル・グラント
  4. クライアント・クレデンシャル・グラント

この全 4 回からなる記事のシリーズでは、上記にリストアップした認可グラントの各タイプを使用して、Java プログラミングで OAuth 2.0 クライアントを実装する方法を説明します。第 2 回目となる今回の記事で説明するのは、クライアント・クレデンシャル・グラントを実装する方法です。記事ではこの認可グラントの詳細を説明し、サンプル・クライアント・コードの内容を解説します。このサンプル・コードを使用すれば、クライアント・クレデンシャル・グラントをサポートする任意の OAuth 2.0 対応サーバーとインターフェースを取ることができます。記事を読み終わる頃には、OAuth 2.0 クライアントの実装について完全に理解し、サンプル・クライアント・コードをダウンロードして独自にコードをテストできるようになっているはずです。

クライアント・クレデンシャル・グラント

このタイプの認可グラントでは、機密クライアントが自身のクライアント・クレデンシャル (または、公開鍵と秘密鍵のペアなど、他のサポートされている認証手段) のみを使用して、認可サーバーにアクセス・トークンを要求することができます。この場合、クライアント自身が管理する、保護されたリソース (つまり、リソース・オーナーがクライアントということです) へのアクセスをクライアントが要求しているという前提です。

図 1 に示すフローは、次のステップで構成されています。

(A) OAuth 2.0 クライアントが自身のクライアント・クレデンシャルを使用して認可サーバーに対して認証を行い、トークン・エンドポイントからアクセス・トークンを発行するよう要求します。

(B) 認可サーバーが OAuth 2.0 クライアントの認証を行い、クライアント・クレデンシャルの有効性を検査します。有効であれば、認可サーバーはアクセス・トークンを発行します。

図 1. クライアント・クレデンシャルのフロー
OAuth 2.0 クライアントと認可サーバーとの間のフローを示す図
OAuth 2.0 クライアントと認可サーバーとの間のフローを示す図

アクセス・トークン・リクエスト

アクセス・トークン・リクエストは、図 1 で記述されているステップ A に相当します。

クライアントがトークン・エンドポイント (認可サーバー) に対して送信するリクエストには、以下のパラメーターが使用されます。これらのパラメーターは、application/x-www-form-urlencoded 形式で送信されます。

  • grant_type: 必須。値は client_credentials に設定されている必要があります。
  • client_id: 必須。クライアント ID です。
  • client_secret: 必須。クライアントのシークレット/パスワードです。
  • scope: オプション。アクセス要求の適用範囲です。

クライアント認証が認可グラントとして使用されているため、認可が追加で必要になることはありません。例えば、クライアントは、トランスポート層セキュリティーを使用して以下の HTTP リクエストを送信します。

リスト 1. クライアントの HTTP リクエスト
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=myApp&client_secret=ab32vr

アクセス・トークン・レスポンス

アクセス・トークン・レスポンスは、図 1 で説明されているステップ B に相当します。アクセス・トークン・リクエストが有効であり、認可されると、認可サーバーはアクセス・トークンを返します。成功した場合のレスポンスをリスト 2 に記載します。

リスト 2. アクセス・トークン・レスポンス
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600,
  "example_parameter":"example_value"
}

リクエストが有効でない場合、または認可されない場合は、認可サーバーから該当するコードのエラー・メッセージが返されます。

セットアップ

サンプル OAuth2.0 クライアントは、「ダウンロード」セクションから入手できる OAuth2.0_client_credentials.zip に含まれています。コードは、ご使用の Eclipse 環境にインポートできるように Java プロジェクトとして編成されています。

前提条件

開発環境を構築して添付のプロジェクトをインポートするには、Eclipse IDE for Java EE Developers が必要になります。Eclipse のダウンロード・ページから Eclipse をダウンロードしてください。

このプロジェクトは、以下の JAR ファイルに依存しています。

  1. commons-codec-1.6.jar
  2. commons-logging-1.1.1.jar
  3. httpclient-4.2.5.jar
  4. httpclient-cache-4.2.5.jar
  5. httpcore-4.2.4.jar
  6. httpmime-4.2.5.jar
  7. json-simple-1.1.1.jar

上記の 1 から 6 までの JAR ファイルは、HttpComponents JAR ファイルにあります。このファイルは Apache HTTP Component プロジェクトからダウンロードすることができます。json-simple-1.1.1.jar ファイルについては、Simple JSON プロジェクトからダウンロードすることができます。必ず、これらの JAR ファイルを Java プロジェクトの lib フォルダーにコピーしてください。

OAuth 2.0 クライアント・コード

今回説明する OAuth 2.0 クライアントが実装するのは、クライアント・クレデンシャル・グラントです。残りの認可グラントのタイプについては、このチュートリアル・シリーズの今後の記事で取り上げ、その都度クライアント・コードを更新します。

入力パラメーター

クライアントの入力パラメーターは、プロジェクトの resources フォルダー内にある Oauth2Client.config プロパティー・ファイルで指定する必要があります。

  • scope: オプション・パラメーター。アクセス要求の適用範囲を表します。サーバーから返されるアクセス・トークンは、このパラメーターで適用範囲として指定されているサービスにのみアクセスすることができます。
  • grant_type: このパラメーターの値は、クライアント・クレデンシャル・グラントを表す client_credentials に設定する必要があります。
  • client_id: リソース・サーバーへのアプリケーション登録時にリソース・サーバーから提供された、クライアント/コンシューマー ID を指定します。
  • client_secret: リソース・サーバーへのアプリケーション登録時にリソース・サーバーから提供された、クライアント/コンシューマー・シークレットを指定します。
  • access_token: 有効かつ認可されたアクセス・トークン・リクエストに対し、認可サーバーから返されたレスポンスに含まれているアクセス・トークンを指定します。このアクセス・トークン・リクエストにクライアント・クレデンシャルを含めて送信すると、アクセス・トークンが返されます。
  • authentication_server_url: トークン・エンドポイントを表します。アクセス・トークンの付与および再生成を要求するリクエストは、いずれもこの URL に送信する必要があります。
  • resource_server_url: 保護されたリソースにアクセスするために接続する必要があるリソース・サーバーの URL を表します。保護されたリソースにアクセスするには、アクセス・トークンを Authorization ヘッダーに含めて、このリソース・サーバーに渡す必要があります。

リスト 3 にクライアント・コードを記載します。

リスト 3. クライアント・ソース・コード
//Load the properties file
Properties config = OauthUtils.getClientConfigProps(OauthConstants.CONFIG_FILE_PATH);
		
//Generate the OAuthDetails bean from the config properties file
Oauth2Details oauthDetails = OauthUtils.createOauthDetails(config);
		
//Validate Input
if(!OauthUtils.isValidInput(oauthDetails)){
 System.out.println("Please provide valid config properties to continue.");
 System.exit(0);
}
		
//Determine operation
if(oauthDetails.isAccessTokenRequest()){
  //Generate new Access token
  String accessToken = OauthUtils.getAccessToken(oauthDetails);
   if(OauthUtils.isValid(accessToken)){
      System.out.println("Successfully generated Access token for client_credentials grant_type: "+accessToken);
   }
   else{
    System.out.println("Could not generate Access token for client_credentials grant_type");
   }
}
		
else {
 //Access protected resource from server using OAuth2.0
 //Response from the resource server must be in Json or Urlencoded or xml
   System.out.println("Resource endpoint url: " + oauthDetails.getResourceServerUrl());
   System.out.println("Attempting to retrieve protected resource");
   OauthUtils.getProtectedResource(oauthDetails);
}

リスト 3 のクライアント・コードは、Oauth2Client.config ファイルに指定された入力パラメーターを読み取り、client_idclient_secretauthentication_server_url の各パラメーター値の有効性を検査します。この config ファイルに指定されているリソース・サーバーの URL が有効であれば、クライアントはその URL で入手可能な、保護されたリソースの取得を試みます。有効でなければ、クライアントは認可サーバーに対してアクセス・トークン・リクエストを送信して、アクセス・トークンを取得するのみです。次のセクションで、保護されたリソースとアクセス・トークンを取得するためのコードについて解説します。

保護されたリソースへのアクセス

リスト 4 のコードは、アクセス・トークンを使用して、保護されたリソースにアクセスする方法を実際に行うコードです。

リスト 4. 保護されたリソースへのアクセス
String resourceURL = oauthDetails.getResourceServerUrl();
				
HttpGet get = new HttpGet(resourceURL);
get.addHeader(OAuthConstants.AUTHORIZATION,
 getAuthorizationHeaderForAccessToken(oauthDetails
  .getAccessToken()));
DefaultHttpClient client = new DefaultHttpClient();
HttpResponse response = null;
int code = -1;
	try {
	response = client.execute(get);
	code = response.getStatusLine().getStatusCode();
	if (code == 401) {
	 // Access token is invalid or expired. Regenerate the access
	 // token
	 System.out
	 .println("Access token is invalid or expired. Regenerating access 	  token....");
	 String accessToken = getAccessToken(oauthDetails);
	 if (isValid(accessToken)) {
	  // update the access token
	  // System.out.println("New access token: " + accessToken);
	   oauthDetails.setAccessToken(accessToken);
	   get.removeHeaders(OAuthConstants.AUTHORIZATION);
	   get.addHeader(OAuthConstants.AUTHORIZATION,
	   getAuthorizationHeaderForAccessToken(oauthDetails
	   .getAccessToken()));
	   get.releaseConnection();
	   response = client.execute(get);
	   code = response.getStatusLine().getStatusCode();
		if (code == 401) {
			throw new RuntimeException(
			"Could not access protected resource. Server returned http 			code: " + code);

		}

	  }
		else {
		 throw new RuntimeException(
		 Could not regenerate access token");
	       }

       }

		handleResponse(response);

注:

  • このメソッドは、config ファイルから取得した値が設定されている OauthDetails Bean を受け取ります。
  • 名前からわかるように、このメソッドは保護されたリソースをリソース・サーバーから取得しようと試みます。そのために、この単純な HttpGet メソッドを作成しているわけです。
  • リソース・サーバーで認証を行うには、アクセス・トークンを Authorization ヘッダーに含めて送信する必要があります。

    例: Authorization: Bearer accessTokenValue

  • リソース・サーバーに対して get リクエストを送信するために、DefaultHttpClient を作成します。
  • リソース・サーバーから受け取ったレスポンス・コードが 401 の場合、認証に使用されたアクセス・トークンはおそらく失効しているか、有効ではありません。
  • 次のステップは、アクセス・トークンを再生成することです (リスト 5 を参照)。
  • アクセス・トークンが正常に再生成されたら、OauthDetails Bean のアクセス・トークン値を更新します。get メソッドの既存の Authorization ヘッダーを新しいアクセス・トークン値で置き換えます。
  • ここで、保護されたリソースにアクセスするために別のリクエストを送信します。
  • アクセス・トークンが有効であると同時にリソース・サーバーの URL が正しければ、コンソール内でレスポンスの内容を確認できるはずです。

失効したアクセス・トークンの再生成

リスト 5 に、失効したアクセス・トークンの再生成を行うコードを記載します。

リスト 5. 失効したアクセス・トークンの再生成
 HttpPost post = new HttpPost(
 oauthDetails.getAuthenticationServerUrl());
 String clientId = oauthDetails.getClientId();
 String clientSecret = oauthDetails.getClientSecret();
 String scope = oauthDetails.getScope();

 List<BasicNameValuePair> parametersBody =
 new ArrayList<BasicNameValuePair>();
 parametersBody.add(new BasicNameValuePair(OAuthConstants.GRANT_TYPE,
 oauthDetails.getGrantType()));
 parametersBody.add(new BasicNameValuePair(OAuthConstants.Client_ID,
 clientId));
 parametersBody.add(new BasicNameValuePair(OAuthConstants.Client_Secret,
 clientSecret));

 
 if (isValid(scope)) {
	parametersBody.add(new BasicNameValuePair
      (OAuthConstants.SCOPE,scope));
  }

 DefaultHttpClient client = new DefaultHttpClient();
 HttpResponse response = null;
 String accessToken = null;
   try {
	post.setEntity(new UrlEncodedFormEntity(parametersBody,
       HTTP.UTF_8));
	
	response = client.execute(post);
	int code = response.getStatusLine().getStatusCode();
	if (code == 401) {
	System.out.println("Authorization
       server expects Basic authentication");
	// Add Basic Authorization header
	post.addHeader(
	OAuthConstants.AUTHORIZATION,
	getBasicAuthorizationHeader(oauthDetails.clientId,
	clientSecret));
	System.out.println("Retry with client credentials");
	post.releaseConnection();
	response = client.execute(post);
	code = response.getStatusLine().getStatusCode();
	
	if (code == 401) {
	throw new RuntimeException(
	"Could not retrieve access token for client: "
	clientId);
	   }
        }
      }
	Map<String, String> map = handleResponse(response);
	accessToken = map.get(OAuthConstants.ACCESS_TOKEN);
	} catch (ClientProtocolException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
	} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
	}

	return accessToken;

注:

  • このメソッドは HttpPost リクエストを生成して、認可サーバーの URL に対して送信します。
  • POST リクエストでは、ペイロードの一部として、client_idclient_secret、そしてオプションで scope を URL エンコード・パラメーターとして送信します。
  • OAuth 2.0 認可フレームワークに従うと、クライアントはアクセス・トークン・リクエストを送信する際に、Authorization ヘッダーにクライアント・クレデンシャル、またはサーバーから認証用に提供されたその他のクレデンシャルを設定する必要がありますが、この要件は認可サーバーの実装に依存します。このクライアント・コードでは、基本認証ヘッダーを追加せずに初期リクエストを送信します。サーバーが不認可レスポンスを返した場合、クライアントはクレデンシャルを使用した認証を行うために、リクエストを再び送信します。
  • OAuth 2.0 では、アクセス・トークン・レスポンスを JSON フォーマットで送信するように規定していますが、柔軟性を持たせるために、このコードにはユーティリティー・メソッドを追加し、サーバーからの XML エンコードまたは URL エンコードされたレスポンスにも対処するようにしました。

OAuth 2.0 クライアントのテスト

このセクションでは、OAuth 2.0 対応エンドポイントをセットアップして、そのエンドポイントに対してクライアントをテストする方法を説明します。

エンドポイントを使用してクライアントをテストする

このクライアントは、Twitter と、IBM Websphere Application Server および IBM DataPower といった OAuth 2.0 対応の IBM エンドポイントを使用してテスト済みです。

OAuth 2.0 エンドポイントを Websphere Application Server 上にセットアップする手順は、「Using Oauth: Enabling the OAuth service provider in WebSphere Application Server」で説明されています。

クライアントを実行する

OAuth 2.0 対応サーバーのセットアップが完了したら、クライアントをテストして保護された情報をサーバーから取得することができます。

  • このチュートリアルに付属の Java プロジェクトを Eclipse ワークスペースにインポートします。
  • 依存関係のある JAR ファイルをダウンロードしてプロジェクトの lib フォルダーにコピーします。
  • resources/com/ibm/oauth/Oauth2Client.config ファイルまでナビゲートして、client_idclient_secretauthorization_server の URL の値を入力します。
  • Eclipse で Oauth2Client.java を開いて実行します。

アクセス・トークン出力

コンソール・ウィンドウに、リスト 6 に示す出力が表示されるはずです。

リスト 6. アクセス・トークン出力
Resource server URL is null. Will assume request is for generating Access token
Validated Input

********** Response Received **********
  expires_in = 3600
  token_type = bearer
  scope =
  access_token = mc20Tn3Br8raUvCrBEap3VYMbErGXshjiXYFAwEB
Successfully generated Access token for client_credentials grant_type: mc20Tn3Br8raUvCrBEap3VYMbErGXshjiXYFAwEB

サーバーからユーザー情報を取得する

アクセス・トークンを入手した後は、WebSphere Application Server 上にホストされている、OAuth 2.0 認証を要求する Web アプリケーションに対してリクエストを送信することができます。

  • Oauth2Client.confg ファイルのアクセス・トークンを更新し、リソース・サーバーの URL を示すプロパティーにテスト対象のリソース・サーバーの URL を設定します。
  • Oauth2Client.java をもう一度実行します。

コンソール・ウィンドウに、リスト 7 に示す出力が表示されるはずです。

リスト 7. ユーザー情報の取得
Resource endpoint url: https://localhost/protectedResource
Attempting to retrieve protected resource

********** Response Received **********
{
 "Author": "Varun Ojha",
 "Authenticatation": "Oauth2.0",
 "Result": "Success"
}

上記の出力に示されているとおり、OAuth 2.0 を使用した認証によって、Web アプリケーションに正常にアクセスすることができます。構成ファイルに提供されたアクセス・トークンが失効すると、クライアントは次のリクエストで自動的にアクセス・トークンを再生成し、その再生成されたアクセス・トークンを使用して、リソース・サーバーの URL で入手可能な、保護されたリソースを取得します。

まとめ

このチュートリアルでは、OAuth クライアント・クレデンシャルの基礎を学びました。このチュートリアルで説明したのは、複数の OAuth 2.0 対応エンドポイントに接続して、保護されたリソースをそれらのエンドポイントから取得するために、汎用の OAuth 2.0 クライアントを Java プログラミングで作成する方法です。ユーザーが簡単に Eclipse ワークスペースのプロジェクトにインポートしてテストを開始できるように、サンプル・クライアントを Java プロジェクトとして用意しておきました。このチュートリアルでは今後、OAuth 2.0 認可フレームワークで概説されている残りの 2 つのタイプの認可グラントを取り上げます。


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


関連トピック


コメント

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=セキュリティ, Java technology
ArticleID=997345
ArticleTitle=Java プログラミングで実装する OAuth 2.0 クライアント: 第 2 回 クライアント・クレデンシャル・グラント
publish-date=04022015