原文出處:IBM - Varun Ojha
概述OAuth 是一個開放的授權標準,允許客戶端代表一個資源所有者獲得訪問受保護服務器資源的訪問權。資源所有者可以是另一個客戶端或最終用戶。OAuth 還可以幫助最終用戶將對其服務器資源的訪問權限授權給第三方,而不必共享其憑據,比如用戶名和密碼。本系列文章遵循 RFC 6749 中所列出的 OAuth 2.0 授權框架。可以在 Internet Engineering Task Force 的網站上找到 RFC 6749 中列出的完整 OAuth 2.0 授權框架(請參閱參考資料)。
授權批準授權批準是一種憑據,可代表資源所有者用來訪問受保護資源的權限。客戶端使用此憑據獲取訪問令牌。訪問令牌最終與請求一起發送,以便訪問受保護資源。OAuth 2.0 定義了四種授權類型:
本文是由四部分組成的系列中的第 1 部分,將引導您使用上面列出的每種授權類型在 Java™ 編程中實現 OAuth 2.0 客戶端。在第 1 部分中,我會告訴大家如何實現資源所有者密碼憑據授權。本文詳細介紹各種授權,并解釋示例客戶端代碼,此代碼可用于兼容 OAuth 2.0 的任何服務器接口,以支持此授權。在本文的最后,您應該對客戶端實現有全面的了解,并準備好下載示例客戶端代碼,自己進行測試。
資源所有者密碼憑據授權當資源所有者對客戶端有高度信任時,資源所有者密碼憑據授權類型是可行的。此授權類型適合于能夠獲取資源所有者的用戶名和密碼的客戶端。對于使用 HTTP 基礎的現有企業客戶端,或者想遷移到 OAuth 的摘要式身份驗證,該授權最有用。然后,通過利用現有憑據來生成一個訪問令牌,然后就可以實現遷移。
例如,Salesforce.com 添加了 OAuth 2.0 作為對其現有基礎架構的一個授權機制。對于現有的客戶端轉變為這種授權方案,資源所有者密碼憑據授權將是最方便的,因為他們只需使用現有的帳戶詳細信息(比如用戶名和密碼)來獲取訪問令牌。
圖 1. 資源所有者密碼憑據流
在 圖 1 中所示的流程包括以下步驟:
對應于第二個步驟的訪問令牌請求如 圖 1 所示。
客戶端對令牌端點(授權服務器)發出請求,采用 application/x-www-form-urlencoded 格式發送以下參數。
grant_type:必選項。必須將其值設置為 “passWord”username:必選項。資源所有者的用戶名。password:必選項。資源所有者密碼。scope:可選項。訪問請求的范圍如果客戶端類型是機密的,或客戶端獲得了客戶端憑據(或者被分配了其他身份驗證要求),那么客戶端必須向授權服務器進行身份驗證。例如,客戶端使用傳輸層安全性發出下列 HTTP 請求。
清單 1. 向授權服務器進行身份驗證| 12345 | POST /token HTTP/1.1Host: server.example.comAuthorization:Basic czZCaGRSa3F0MzpnWDFmQmF0M2JWContent-Type: application/x-www-form-urlencodedgrant_type=password&username=varun&password=ab32vr |
對應于上述步驟 C 的訪問令牌響應如圖 1所示。如果訪問令牌請求是有效的,并且獲得了授權,那么授權服務器將返回訪問令牌和一個可選的刷新令牌。清單 2 顯示了一個成功響應的示例。
清單 2. 成功的訪問令牌響應| 123456789101112 | HTTP/1.1 200 OKContent-Type: application/json;charset=UTF-8Cache-Control: no-storePRagma: 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 文件:
在前六項中提到的 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,通過將授權標頭中的訪問令牌傳遞給它來訪問受保護的資源。| 123456789101112131415161718192021222324252627282930313233343536 | 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 tokenSystem.out.println("Retrieving Access Token");OAuth2Details oauthDetails = OAuthUtils.createOAuthDetails(config);String accessToken = OAuthUtils.getAccessToken(oauthDetails);System.out.println("Successfully retrieved Access tokenfor Password Grant:" + accessToken);}else {// Response from the resource server must be in Json or//Urlencoded or xmlSystem.out.println("Resource endpoint url:" + resourceServerUrl);System.out.println("Attempting to retrieve protected resource");OAuthUtils.getProtectedResource(config);} |
在清單 3中的客戶端代碼讀取 Oauth2Client.config 文件中所提供的輸入參數。username、password、authentication server url和grant type的有效值是強制性的。如果配置文件中所提供的資源服務器 URL 是有效的,那么客戶端會嘗試檢索該 URL 中提供的受保護資源。否則,客戶端只對授權服務器發出訪問令牌請求,并取回訪問令牌。以下部分說明了負責檢索受保護資源和訪問令牌的代碼。
清單 4 中的代碼演示了如何使用訪問令牌來訪問受保護的資源。
清單 4. 訪問受保護資源| 123456789101112131415161718192021222324252627282930313233343536373839404142434445 | 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 tokenSystem.out.println("Access token is invalidor 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 notaccess protected resource.Server returned http code:"+ code);}} else {throw new RuntimeException("Could notregenerate access token");}}handleResponse(response); |
OauthDetailsbean。HttpGet方法。DefaultHttpClient對資源服務器發出一個get請求。OauthDetailsbean 中的訪問令牌值。用新的訪問令牌值替換get方法中現有的 Authorization 標頭。清單 5 中的代碼將會處理已過期訪問令牌的重新生成。
清單 5. 重新生成過期的訪問令牌| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778 | 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("Authorizationserver expects Basic authentication");// Add Basic Authorization headerpost.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 blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}return accessToken; |
HttpPost請求,并獲得身份驗證服務器的 URL。Post請求以 URL 編碼參數的形式發送username、password,以及可選的scope,將它們作為有效載荷的一部分。client_id和client_secret作為此請求有效載荷的一部分。client_id、client_secret和scope的值不為空,那么它們也將作為有效載荷的一部分被發送。在本節中,我將討論如何建立一個 OAuth 2.0 兼容的端點并用它來測試客戶端。
在 Salesforce.com 注冊Salesforce.com 對于資源所有者密碼憑據授權是一個很好的用例。如果用戶有登錄 Salesforce.com 的憑據,并希望自己的客戶端轉變為 OAuth 2.0 身份驗證,那么他需要做的就是在 Salesforce 注冊自己的應用程序,以獲得客戶端憑據。現在可以用這些客戶端憑據以及他現有的登錄憑據從授權服務器中獲得訪問令牌。
現在,您已經完成了 Salesforce.com 中的注冊,您可以測試客戶端并從服務器檢索受保護的信息。
username、password(追加安全令牌)、client_id、client_secret和authorization server URL的值。您應該在控制臺窗口看到下面的輸出。
| 12345678910 | Retrieving Access TokenencodedBytes dmVybi5vamhhQGdtYWlsL.......********** Response Received **********instance_url = https://ap1.salesforce.comissued_at = 1380106995639signature = LtMjTrmoBbvVfZ6+qT5Un1UioHaV9KIOK7ayQTmJzCg=id = https://login.salesforce.com/id/00D90000000mQaYEAU/00590000001HCB7AAOaccess_token = 00D90000000mQaY!AQ8AQEn0rLDMvxrP9WgY3Blc.......Successfully retrieved Access token for Password Grant:00D90000000mQaY!AQ8AQEn0rLDMvxrP9WgY3Bl...... |
現在,您已經有了訪問令牌和 ID,可以向 Salesforce.com 發出請求,通過使用 OAuth 2.0 進行身份驗證來訪問您的帳戶信息。
id值來填充資源服務器 URL 屬性。您應該在控制臺窗口看到類似于下面的輸出。
清單 6. 輸出| 12345678910111213141516171819202122232425262728293031323334 | Resource endpoint URL: https://login.salesforce.com/id/00D90000000mQaYEAU/00590000001HCB7AAOAttempting 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 = trueactive = trueorganization_id = 00D90000000mQaYEAUnick_name = vern.ojha1....display_name = varun ojhauser_type = STANDARDuser_id = ***********status = {"body":null,"created_date":null}last_name = ojhausername = vern.ojha.....utcOffset = -28800000language = en_USlocale = en_USfirst_name = varunlast_modified_date = 2013-06-04T07:43:42.000+0000id = https://login.salesforce.com/id/00D90000000mQaYEAU/00590000001HCB7AAOemail = 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
新聞熱點
疑難解答