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

developerworks發表於2015-08-10

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

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

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

概述

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

授權批准

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

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

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

資源所有者密碼憑據授權

當資源所有者對客戶端有高度信任時,資源所有者密碼憑據授權型別是可行的。此授權型別適合於能夠獲取資源所有者的使用者名稱和密碼的客戶端。對於使用 HTTP 基礎的現有企業客戶端,或者想遷移到 OAuth 的摘要式身份驗證,該授權最有用。然後,通過利用現有憑據來生成一個訪問令牌,然後就可以實現遷移。

例如,Salesforce.com 新增了 OAuth 2.0 作為對其現有基礎架構的一個授權機制。對於現有的客戶端轉變為這種授權方案,資源所有者密碼憑據授權將是最方便的,因為他們只需使用現有的帳戶詳細資訊(比如使用者名稱和密碼)來獲取訪問令牌。

圖 1. 資源所有者密碼憑據流

Java 程式設計中的 OAuth 2.0 客戶端,第 1 部分: 資源所有者密碼憑據授權

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

  1. 資源所有者提供一個可信的 OAuth 2.0 客戶端,並提供其使用者名稱和密碼。
  2. OAuth 2.0 客戶端對授權伺服器的令牌端點發出訪問令牌請求,其中包括從資源所有者那裡收到的憑據。在發出請求時,OAuth 2.0 客戶端使用由授權伺服器提供的憑據和授權伺服器進行身份驗證。
  3. 授權伺服器對 OAuth 2.0 客戶端進行身份驗證,並驗證資源所有者憑據,如果該憑據是有效的,那麼授權伺服器會頒發一個訪問令牌。

訪問令牌請求

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

客戶端對令牌端點(授權伺服器)發出請求,採用 application/x-www-form-urlencoded 格式傳送以下引數。

  • grant_type:必選項。必須將其值設定為 “password”
  • username:必選項。資源所有者的使用者名稱。
  • password:必選項。資源所有者密碼。
  • scope:可選項。訪問請求的範圍

如果客戶端型別是機密的,或客戶端獲得了客戶端憑據(或者被分配了其他身份驗證要求),那麼客戶端必須向授權伺服器進行身份驗證。例如,客戶端使用傳輸層安全性發出下列 HTTP 請求。

清單 1. 向授權伺服器進行身份驗證
POST /token HTTP/1.1
Host: server.example.com
Authorization:Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=varun&password=ab32vr

訪問令牌響應

對應於上述步驟 C 的訪問令牌響應如 圖 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,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}

如果請求無效,或者是未經授權的,那麼授權伺服器將會使用程式碼返回一個相應的錯誤訊息。

設定

示例 OAuth 2.0 客戶端被附加為可匯入 Eclipse 環境中的 Java 專案。您需要將第三方依賴關係 JAR 檔案下載到 Java 專案中的 lib 資料夾中。

依賴關係 JAR 檔案

該專案使用以下 JAR 檔案:

  • commons-codec-1.6.jar
  • commons-logging-1.1.1.jar
  • httpclient-4.2.5.jar
  • httpclient-cache-4.2.5.jar
  • httpcore-4.2.4.jar
  • httpmime-4.2.5.jar
  • json-simple-1.1.1.jar

在前六項中提到的 JAR 檔案可以在 Http Components JAR 檔案中找到。下載這些檔案和 json-simple-1.1.1.jar 檔案的連結,請參見 更多下載。確保已將下面這些可供下載的 JAR 檔案複製到 Java 專案的 lib 資料夾。

先決條件

下載 Eclipse IDE for Java EE developers,以便設定開發環境,並匯入附加專案。相關的連結請參見 更多下載

OAuth 2.0 客戶端

此處討論的 OAuth 2.0 客戶端實現了資源所有者密碼憑據授權。本系列文章的後續部分將描述其餘授權型別,並繼續更新客戶端程式碼。

輸入引數

使用在示例客戶端程式碼下載(請參閱 Download)中提供的 Oauth2Client.config 屬性檔案向客戶端提供所需的輸入引數。

  • scope:這是一個可選引數。它代表訪問請求的範圍。由伺服器返回的訪問令牌只可以訪問 scope 中提到的服務。
  • grant_type:需要將這個引數設定為 "password",表示資源所有者密碼憑據授權。
  • username:用於登入到資源伺服器的使用者名稱。
  • password:用於登入到資源伺服器的密碼。
  • client_id:註冊應用程式時由資源伺服器提供的客戶端或使用者 ID。
  • client_secret:註冊應用程式時由資源伺服器提供的客戶端或使用者的密碼。
  • access_token:授權伺服器響應有效的和經過授權的訪問令牌請求時返回的訪問令牌。作為該請求的一部分,您的使用者名稱和密碼將用於交換訪問令牌。
  • refresh_token:這是一個可選引數,由授權伺服器在響應訪問令牌請求時返回。然而,大多數端點(比如 Salesforce、IBMWebSphere® Application Server 和 IBM DataPower)對資源所有者密碼憑據授權不返回重新整理令牌。因此,我的客戶端實現不打算考慮重新整理令牌。
  • authenticatation_server_url:這表示令牌端點。批准和重新生成訪問令牌的所有請求都必須傳送到這個 URL。
  • resource_server_url:這表示需要聯絡的資源伺服器的 URL,通過將授權標頭中的訪問令牌傳遞給它來訪問受保護的資源。
清單 3. Oauth2Client 程式碼
 Properties config = OAuthUtils.getClientConfigProps 	(OAuthConstants.CONFIG_FILE_PATH);
 String resourceServerUrl = config.getProperty(OAuthConstants.RESOURCE_SERVER_URL);      
 String username = config.getProperty(OAuthConstants.USERNAME);
 String password = config.getProperty(OAuthConstants.PASSWORD);
 String grantType = config.getProperty(OAuthConstants.GRANT_TYPE);
 String authenticationServerUrl = config
         .getProperty(OAuthConstants.AUTHENTICATION_SERVER_URL);

  if (!OAuthUtils.isValid(username)
    || !OAuthUtils.isValid(password)
    || !OAuthUtils.isValid(authenticationServerUrl)
    || !OAuthUtils.isValid(grantType)) {
  System.out
        .println("Please provide valid values for username, password,
                        authentication server url and grant type");
  System.exit(0);

   }

 if (!OAuthUtils.isValid(resourceServerUrl)) {
 // Resource server url is not valid.
 //Only retrieve the access token
 System.out.println("Retrieving Access Token");
 OAuth2Details oauthDetails = OAuthUtils.createOAuthDetails(config);
 String accessToken = OAuthUtils.getAccessToken(oauthDetails);
 System.out
 .println("Successfully retrieved Access token
  for Password Grant:" + accessToken);
 }
 else {
 // Response from the resource server must be in Json or
 //Urlencoded or xml
 System.out.println("Resource endpoint url:" + resourceServerUrl);
 System.out.println("Attempting to retrieve protected resource");
 OAuthUtils.getProtectedResource(config);
      }

在 清單 3 中的客戶端程式碼讀取 Oauth2Client.config 檔案中所提供的輸入引數。usernamepassword、 authentication server url 和 grant type 的有效值是強制性的。如果配置檔案中所提供的資源伺服器 URL 是有效的,那麼客戶端會嘗試檢索該 URL 中提供的受保護資源。否則,客戶端只對授權伺服器發出訪問令牌請求,並取回訪問令牌。以下部分說明了負責檢索受保護資源和訪問令牌的程式碼。

訪問受保護資源

清單 4 中的程式碼演示瞭如何使用訪問令牌來訪問受保護的資源。

清單 4. 訪問受保護資源
String resourceURL =
config.getProperty(OAuthConstants.RESOURCE_SERVER_URL);
OAuth2Details oauthDetails = createOAuthDetails(config);
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 >= 400) {
      // 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 >= 400) {
	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 請求。
  • 如果從資源伺服器收到的響應程式碼是 403 或 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.USERNAME,
 oauthDetails.getUsername()));
 parametersBody.add(new BasicNameValuePair(OAuthConstants.PASSWORD,
 oauthDetails.getPassword()));

 if (isValid(clientId)) {
	parametersBody.add(new BasicNameValuePair
      (OAuthConstants.CLIENT_ID,clientId));
  }
 if (isValid(clientSecret)) {
	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 >= 400) {
	System.out.println("Authorization
       server expects Basic authentication");
	// Add Basic Authorization header
	post.addHeader(
	OAuthConstants.AUTHORIZATION,
	getBasicAuthorizationHeader(oauthDetails.getUsername(),
	oauthDetails.getPassword()));
	System.out.println("Retry with login credentials");
	post.releaseConnection();
	response = client.execute(post);
	code = response.getStatusLine().getStatusCode();
	if (code >= 400) {
	System.out.println("Retry with client credentials");
	post.removeHeaders(OAuthConstants.AUTHORIZATION);
	post.addHeader(
	OAuthConstants.AUTHORIZATION,
       getBasicAuthorizationHeader(
	 oauthDetails.getClientId(),
	oauthDetails.getClientSecret()));
	post.releaseConnection();
	response = client.execute(post);
	code = response.getStatusLine().getStatusCode();
	if (code >= 400) {
	throw new RuntimeException(
	"Could not retrieve access token for user:"
	oauthDetails.getUsername());
	   }
        }
      }
	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 編碼引數的形式傳送 usernamepassword,以及可選的 scope,將它們作為有效載荷的一部分。
  • 有些授權伺服器還會要求您傳送 client_id 和 client_secret 作為此請求有效載荷的一部分。
  • 如果 client_idclient_secret 和 scope 的值不為空,那麼它們也將作為有效載荷的一部分被髮送。
  • 按照 OAuth 2.0 授權框架,客戶端應該使用客戶端憑據,或使用在發出訪問令牌請求時由伺服器所提供的其他任何憑據來設定 Authorization 標頭。但是,這受制於授權伺服器實現。客戶端程式碼發出初始請求時無需新增基本身份驗證標頭。如果伺服器返回一個未經授權的響應,客戶端隨後試圖通過登入和客戶端憑據進行身份驗證。
  • OAuth 2.0 規定,需要採用 JSON 格式來傳送訪問令牌響應。但為了靈活性,我還新增了實用程式方法來處理來自伺服器的 XML 編碼或 URL 編碼的響應。

測試客戶端

在本節中,我將討論如何建立一個 OAuth 2.0 相容的端點並用它來測試客戶端。

在 Salesforce.com 註冊

Salesforce.com 對於資源所有者密碼憑據授權是一個很好的用例。如果使用者有登入 Salesforce.com 的憑據,並希望自己的客戶端轉變為 OAuth 2.0 身份驗證,那麼他需要做的就是在 Salesforce 註冊自己的應用程式,以獲得客戶端憑據。現在可以用這些客戶端憑據以及他現有的登入憑據從授權伺服器中獲得訪問令牌。

  1. 如果您以前沒有在 Salesforce.com 註冊過,請立即註冊。請參閱 參考資料 。
  2. 單擊 Login,然後單擊 Sign up for free
  3. 完成註冊,獲得您的憑據。
  4. 除了使用者名稱和密碼之外,您還將獲得一個安全令牌。您提供的用於發出訪問令牌請求的密碼必須是您的密碼和安全令牌的串聯。(示例:password12312123)。
  5. 有關如何在 salesforce.com 中建立應用程式,請參閱 參考資料 中有用的文章連結。

執行客戶端

現在,您已經完成了 Salesforce.com 中的註冊,您可以測試客戶端並從伺服器檢索受保護的資訊。

  • 將本文中討論的 Java 專案匯入到 Eclipse 工作區(請參閱 參考資料)。
  • 下載依賴關係 JAR 檔案,並將其複製到專案的 lib 資料夾中(請參閱 更多下載 )。
  • 導航到 resources/com/ibm/oauth/Oauth2Client.config 檔案,並填寫 usernamepassword(追加安全令牌)、client_idclient_secret 和 authorization server URL 的值。
  • 開啟 Oauth2Client.java 並執行它。

訪問令牌輸出

您應該在控制檯視窗看到下面的輸出。

Retrieving Access Token
encodedBytes dmVybi5vamhhQGdtYWlsL.......

********** Response Received **********
  instance_url = https://ap1.salesforce.com
  issued_at = 1380106995639
  signature = LtMjTrmoBbvVfZ6+qT5Un1UioHaV9KIOK7ayQTmJzCg=
  id = https://login.salesforce.com/id/00D90000000mQaYEAU/00590000001HCB7AAO
  access_token = 00D90000000mQaY!AQ8AQEn0rLDMvxrP9WgY3Blc.......
Successfully retrieved Access token for Password Grant:00D90000000mQaY!AQ8AQEn0rLDMvxrP9WgY3Bl......

從 Salesforce.com 檢索使用者資訊

現在,您已經有了訪問令牌和 ID,可以向 Salesforce.com 發出請求,通過使用 OAuth 2.0 進行身份驗證來訪問您的帳戶資訊。

  • 用訪問令牌更新 Oauth2Client.confg 檔案,並使用作為響應的一部分返回的 id 值來填充資源伺服器 URL 屬性。
  • 再次執行 Oauth2Client.java。

輸出

您應該在控制檯視窗看到類似於下面的輸出。

清單 6. 輸出
Resource endpoint URL: https://login.salesforce.com/id/00D90000000mQaYEAU/00590000001HCB7AAO
Attempting to retrieve protected resource
********** Response Received **********
photos = {"thumbnail":"https:////c.ap1.content.force.com//profilephoto//005//T","picture":"https:///
/c.ap1.content.force.com//profilephoto//005//F"}
urls =
{"enterprise":"https:////ap1.salesforce.com//services//Soap//c//{version}//00D90000000mQaY","sobjects":
"https:////ap1.salesforce.com//services//data//v{version}//sobjects//","partner":"https:///
/ap1.salesforce.com//services//Soap//u//{version}//00D90000000mQaY","search":"https:///
/ap1.salesforce.com//services//data//v{version}//search//","query":"https:////ap1.salesforce.com/
/services//data//v{version}//query//","users":"https:////ap1.salesforce.com//services//data//v{version}/
/chatter//users","profile":"https:////ap1.salesforce.com//00590000001HCB7AAO","metadata":"https:///
/ap1.salesforce.com//services//Soap//m//{version}//00D90000000mQaY","rest":"https:////ap1.salesforce.com/
/services//data//v{version}//","groups":"https:////ap1.salesforce.com//services//data//v{version}/
/chatter//groups","feeds":"https:////ap1.salesforce.com//services//data//v{version}//chatter//feeds",
"recent":"https:////ap1.salesforce.com//services//data//v{version}//recent//","feed_items":"https://
//ap1.salesforce.com//services//data//v{version}//chatter//feed-items"}
  asserted_user = true
  active = true
  organization_id = 00D90000000mQaYEAU
  nick_name = vern.ojha1....
  display_name = varun ojha
  user_type = STANDARD
  user_id = ***********
  status = {"body":null,"created_date":null}
  last_name = ojha
  username = vern.ojha.....
  utcOffset = -28800000
  language = en_US
  locale = en_US
  first_name = varun
  last_modified_date = 2013-06-04T07:43:42.000+0000
  id = https://login.salesforce.com/id/00D90000000mQaYEAU/00590000001HCB7AAO
  email = vern.ojha@gmail.com

如您所見,您可以通過使用 OAuth 2.0 進行身份驗證,成功獲取使用者資訊。在配置檔案中提供的訪問令牌過期後,客戶端將會自動重新生成訪問令牌,並使用它來檢索在資源伺服器 URL 中提供的受保護資源。

用 IBM 端點測試客戶端

客戶端也已經成功通過 OAuth 2.0 相容的 IBM 端點的測試,即 IBM WebSphere Application Server 和 IBM DataPower。請參閱 參考資料 的連結,“使用 OAuth:在 WebSphere Application Server 中啟用 OAuth 服務提供程式”,這是一個非常好的資源,介紹瞭如何在 WebSphere Application Server 上設定 OAuth 2.0。

在您的應用伺服器上設定了 OAuth 2.0 後,客戶端所需的輸入與 Salesforce.com 演示中的相同。

結束語

本文是該系列文章的第一部分,闡述了資源所有者密碼憑據授權的基礎知識。本文演示瞭如何在 Java 程式設計中編寫一個通用的 OAuth 2.0 客戶端,以連線到 OAuth 2.0 相容的多個端點,並從中獲取受保護的資源。示例客戶端被附加為一個 Java 專案,以使您能夠迅速匯入專案到 Eclipse 工作區,並開始測試。在本系列文章的後續部分中,我將介紹在 OAuth 2.0 授權框架中列出的其餘三種授權型別。在後續文章中,客戶端程式碼將會獲得更新,以反映這些授權型別及特性,比如釋出到某個資源伺服器和處理 SSL。

下載

描述 名字 大小
示例客戶端程式碼 OAuth20.zip 19KB

相關文章