概述
OAuth 是一个开放的授权标准,允许客户端代表一个资源所有者获得对受保护服务器资源的访问权限。资源所有者可以是另一个客户端或最终用户。OAuth 还可以帮助最终用户将对其服务器资源的访问权限授权给第三方,而不必共享其凭据,比如用户名和密码。本系列文章遵从 RFC6749 中列出的 OAuth 2.0 授权框架。可以在 Internet Engineering Task Force 的网站上找到 RFC 6749 中列出的完整 OAuth 2.0 授权框架(请参阅 )。
授权批准
授权批准是一种凭据,可代表资源所有者用来获得访问受保护资源的访问权。客户端使用此凭据获取访问令牌,而且此访问令牌最终将与请求一起发送,以便访问受保护资源。OAuth 2.0 定义了四种授权类型:
- 授权码
- 隐式
- 资源所有者密码凭据
- 客户端凭据
这个文章系列由四个部分组成,将引导您使用上面列出的每种授权类型在 Java™ 编程中实现 OAuth 2.0 客户端。在第 2 部分中,我将解释如何实现客户端凭据授权。本文将详细介绍这种授权,并解释示例客户端代码,此代码可用于兼容 OAuth 2.0 的任何服务器接口,以便支持这种授权。在本文的最后,您应该对客户端实现有了全面的了解,并准备好下载示例客户端代码,自己进行测试。
客户端凭据授权
在此授权中,机密客户端可以只使用其客户端凭据(或其他可支持的身份验证方法,比如公钥/私钥对)从授权服务器请求一个访问令牌。假设客户端在请求访问在其自身控制下的受保护资源(客户端是资源所有者)。
图 1 中所示的流程包括以下步骤:
(A) OAuth 2.0 客户端使用其客户端凭据和授权服务器进行身份验证,并从令牌端点请求访问令牌
(B) 授权服务器对 OAuth 2.0 客户端进行身份验证,并验证客户端凭据,如果凭据是有效的,那么授权服务器将会颁发一个访问令牌。
图 1. 客户端凭据流
访问令牌请求
对应于 步骤 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 文件:
- 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
第 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_id
、client_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_id
、client_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_id
、client_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 授权框架中列出的其余两种授权类型。
转自http://www.ibm.com/developerworks/cn/java/se-oauthjavapt2/index.html