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 客户端进行身份验证,并验证客户端凭据,如果凭据是有效的,那么授权服务器将会颁发一个访问令牌。
对应于 步骤 A 的访问令牌请求如 图 1 所示。
客户端对令牌端点(授权服务器)发出请求,采用 application/x-www-form-urlencoded
格式发送以下参数。
grant_type
:必选项。该值必须设置为 "client_credentials
"client_id
:必选项。客户端 ID。client_secret
:必选项。客户端密钥/密码。scope
:可选项。访问请求的范围因为客户端身份验证被用作授权批准,所以不需要额外的授权。例如,客户端利用传输层安全性发出下列 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 所示。
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 至第 6 点中提到的 JAR 文件可以在 HttpComponents JAR 文件中找到。可以从 Apache HTTP Component 项目 下载它。可以从 Simple JSON 项目页面 下载 json-simple-1.1.1.jar 文件。确保已将这些 JAR 文件复制到 Java 项目的 lib
文件夹中。
此处讨论的 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 所示。
//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 中的代码演示了如何使用访问令牌来访问受保护的资源。
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:Bearer accessTokenValue
DefaultHttpClient
,向资源服务器发出一个 get
请求。OauthDetails
bean 中的访问令牌值。用新的访问令牌值替换 get
方法中现有的 Authorization 标头。清单 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。client_id
、client_secret
,以及可选的 scope
,将它们作为有效载荷的一部分。本节将介绍如何建立一个 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 兼容的服务器,您可以测试客户端,并从服务器检索受保护的信息。
client_id
、client_secret
和 authorization_server
URL 的值。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 上的 Web 应用程序发出请求,该服务器要求使用 OAuth 2.0 进行身份验证。
您应该在控制台窗口中看到输出,如 清单 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 |