Java OAuth 2.0 客戶端程式設計(二): 客戶端憑據授權

developerworks發表於2015-08-13

Java OAuth 2.0 客戶端程式設計(一): 資源所有者密碼憑據授權

Java OAuth 2.0 客戶端程式設計(二): 客戶端憑據授權

Java OAuth 2.0 客戶端程式設計(三):認證碼授權

概述

OAuth 是一個開放的授權標準,允許客戶端代表一個資源所有者獲得對受保護伺服器資源的訪問許可權。資源所有者可以是另一個客戶端或終端使用者。OAuth 還可以幫助終端使用者將對其伺服器資源的訪問許可權授權給第三方,而不必共享其憑據,比如使用者名稱和密碼。本系列文章遵從 RFC6749 中列出的 OAuth 2.0 授權框架。可以在 Internet Engineering Task Force 的網站上找到 RFC 6749 中列出的完整 OAuth 2.0 授權框架(請參閱 )。

授權批准

授權批准是一種憑據,可代表資源所有者用來獲得訪問受保護資源的訪問權。客戶端使用此憑據獲取訪問令牌,而且此訪問令牌最終將與請求一起傳送,以便訪問受保護資源。OAuth 2.0 定義了四種授權型別:

  1. 授權碼
  2. 隱式
  3. 資源所有者密碼憑據
  4. 客戶端憑據

這個文章系列由四個部分組成,將引導您使用上面列出的每種授權型別在 Java™ 程式設計中實現 OAuth 2.0 客戶端。在第 2 部分中,我將解釋如何實現客戶端憑據授權。本文將詳細介紹這種授權,並解釋示例客戶端程式碼,此程式碼可用於相容 OAuth 2.0 的任何伺服器介面,以便支援這種授權。在本文的最後,您應該對客戶端實現有了全面的瞭解,並準備好下載示例客戶端程式碼,自己進行測試。

客戶端憑據授權

在此授權中,機密客戶端可以只使用其客戶端憑據(或其他可支援的身份驗證方法,比如公鑰/私鑰對)從授權伺服器請求一個訪問令牌。假設客戶端在請求訪問在其自身控制下的受保護資源(客戶端是資源所有者)。

圖 1 中所示的流程包括以下步驟:

(A) OAuth 2.0 客戶端使用其客戶端憑據和授權伺服器進行身份驗證,並從令牌端點請求訪問令牌

(B) 授權伺服器對 OAuth 2.0 客戶端進行身份驗證,並驗證客戶端憑據,如果憑據是有效的,那麼授權伺服器將會頒發一個訪問令牌。

圖 1. 客戶端憑據流

Java 程式設計中的 OAuth 2.0 客戶端,第 2 部分: 客戶端憑據授權

訪問令牌請求

對應於 步驟 A 的訪問令牌請求如 圖 1 所示。

客戶端對令牌端點(授權伺服器)發出請求,採用 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

訪問令牌響應

對應於 步驟 B 的訪問令牌響應該如 圖 1 所示。如果訪問令牌請求是有效的,而且獲得了授權,那麼授權伺服器將會返回訪問令牌。成功的響應如 清單 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_client_credentials.zip 檔案中提供了示例 Outh2.0 客戶端。其程式碼被組織為可匯入 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 專案 下載它。可以從 Simple JSON 專案頁面 下載 json-simple-1.1.1.jar 檔案。確保已將這些 JAR 檔案複製到 Java 專案的 lib資料夾中。

OAuth 2.0 客戶端程式碼

此處討論的 OAuth 2.0 客戶端實現了客戶端憑據授權。在本教程系列的後續部分中,將討論其餘授權型別,並更新客戶端程式碼。

輸入引數

使用專案的 resources 資料夾中提供的 Oauth2Client.config 屬性檔案向客戶端提供所需的輸入引數。

  • scope:這是一個可選引數。它代表訪問請求的範圍。由伺服器返回的訪問令牌只可以訪問 scope 中提到的服務。
  • grant_type:需要將這個引數設定為 client_credentials,它表示客戶端憑據授權。
  • client_id:註冊應用程式時由資源伺服器提供的客戶端或使用者 ID。
  • client_secret:註冊應用程式時由資源伺服器提供的客戶端或使用者的密碼。
  • access_token:授權伺服器響應有效的和經過授權的訪問令牌請求時所返回的訪問令牌。作為該請求的一部分,您的客戶端憑據可用於交換訪問令牌。
  • authentication_server_url:這表示令牌端點。批准和重新生成訪問令牌的所有請求都必須傳送到這個 URL。
  • resource_server_url:這表示需要聯絡的資源伺服器的 URL,通過將授權標頭中的訪問令牌傳遞給它,訪問受保護的資源。

客戶端程式碼如 清單 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_secret 和authentication_server_url 的值。如果配置檔案中提供的資源伺服器 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);

注意:

  • 此方法使用從配置檔案檢索到的值來接受 OauthDetails bean。
  • 顧名思義,這種方法會嘗試從資源伺服器中檢索受保護的資源。因此,我們建立一個簡單的 HttpGet 方法。
  • 為了向資源伺服器進行身份驗證,需要將訪問令牌作為 Authorization 標頭的一部分傳送。例如:Authorization:Bearer accessTokenValue
  • 建立一個 DefaultHttpClient,向資源伺服器發出一個 get 請求。
  • 如果從資源伺服器收到的響應程式碼是 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 請求以 URL 編碼引數的形式傳送 client_idclient_secret,以及可選的 scope,將它們作為有效載荷的一部分。
  • 按照 OAuth 2.0 授權框架,客戶端應該使用客戶端憑據,或使用在發出訪問令牌請求時由伺服器提供的其他任何憑據設定 Authorization 標頭。但是,這受制於授權伺服器實現。客戶端程式碼發出初始請求時無需新增基本身份驗證標頭。如果伺服器返回一個未經授權的響應,那麼客戶端隨後會試圖使用客戶端憑據進行身份驗證。
  • OAuth 2.0 規定,應該採用 JSON 格式來傳送訪問令牌響應。但為了靈活性,我還新增了實用程式方法來處理來自伺服器的 XML 編碼或 URL 編碼的響應。

測試 OAuth 2.0 客戶端

本節將介紹如何建立一個 OAuth 2.0 相容的端點,並用它來測試客戶端。

用端點來測試客戶端

客戶端已經用 Twitter 和 OAuth 2.0 相容的 IBM 端點(比如 IBM® Websphere® Application Server 和 IBM DataPower™)完成了成功的測試。

在 WebSphere Application Server 中啟用 OAuth 服務提供程式” 中可以找到有關在 WebSphere Application Server 上設定 OAuth 2.0 端點的說明。

執行客戶端

現在,您已經設定了 OAuth 2.0 相容的伺服器,您可以測試客戶端,並從伺服器檢索受保護的資訊。

  • 將附加在本教程的 Java 專案匯入到 Eclipse 工作區中。
  • 下載依賴關係 JAR 檔案,並將其複製到專案的 lib 資料夾中。
  • 導航到 resources/com/ibm/oauth/Oauth2Client.config 檔案並填充 client_idclient_secret 和 authorization_server URL 的值。
  • 在 Eclipse 中開啟 Oauth2Client.java 並執行它。

訪問令牌輸出

您應該在控制檯視窗中看到如清單 6 所示的輸出。 清單 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 上的 Web 應用程式發出請求,該伺服器要求使用 OAuth 2.0 進行身份驗證。

  • 用訪問令牌更新 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 客戶端憑據的基礎知識。本教程演示瞭如何在 Java 程式設計中編寫一個通用的 OAuth 2.0 客戶端,以便連線到 OAuth 2.0 相容的多個端點,並從中獲取受保護的資源。示例客戶端被作為一個 Java 專案附加,讓您能夠迅速將專案匯入到 Eclipse 工作區,並開始測試。在本教程系列的後續部分中,將介紹在 OAuth 2.0 授權框架中列出的其餘兩種授權型別。

下載

描述 名字 大小
Sample OAuth client OAuth2.0_client_credentials_2.zip 982KB

相關文章