OAuth 是一个开放的授权标准,允许客户端代表一个资源所有者获得访问受保护服务器资源的访问权。资源所有者可以是另一个客户端或最终用户。OAuth 还可以帮助最终用户将对其服务器资源的访问权限授权给第三方,而不必共享其凭据,比如用户名和密码。本系列文章遵从 RFC6749 中列出的 OAuth 2.0 授权框架。可以在 Internet Engineering Task Force 的网站上找到 RFC 6749 中列出的完整 OAuth 2.0 授权框架(请参阅 参考资料)。
授权批准是一种凭据,可代表资源所有者用来获得访问受保护资源的访问权。客户端使用此凭据获取访问令牌,而且此访问令牌最终将与请求一起发送,以便访问受保护的资源。OAuth 2.0 定义了四种授权类型:
这个文章系列由四部分组成,将会引导您使用上面列出的每种授权类型在 Java™ 编程中实现 OAuth 2.0 客户端。在第 3 部分中,我将解释如何实现认证码授权。本文详细介绍此类授权,并解释示例客户端代码,此代码可用于兼容 OAuth 2.0 的任何服务器接口,以便支持此授权。在本文的最后,您应该对客户端实现有全面的了解,并准备好下载示例客户端代码,自己进行测试。
此授权已针对机密性客户端进行了优化,用于获得访问令牌和刷新令牌。这是一个基于重定向的流程,因此,客户端必须能够与资源所有者的用户代理(通常是 Web 浏览器)进行交互,并且还必须能够(通过重定向)接收来自授权服务器的传入请求。
认证码授权如 图 1 所示。
图 1 中所示的流程包括以下步骤:
对应于步骤 (A) 和 (B) 的授权码请求如 图 1 所示。在步骤 (A) 中,客户端采用 application/x-www-form-urlencoded
格式向授权服务器发出一个请求,如 清单 1 所示。
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1 Host: server.example.com
该请求必须包含以下参数:
response_type
:必选项。该值必须设置为 code
。client_id
:必选项。客户端 ID。redirect_uri
:必选项。用于用户代理重定向。scope
:可选项。访问请求的范围。state
:可选项。保持请求和回调之间的状态。在授权服务器验证请求后,服务器将一个 HTTP 重定向代码 302 响应发送回客户端。该响应还将在 http Location
标头中包括一个重定向 URI。在步骤 (B) 中,客户端必须将用户代理(通常是 Web 浏览器)重定向到此 URI。这种重定向 URI 通常是一个登录页面,资源所有者可以使用其凭据进行登录,并批准/撤销客户端的访问请求。
授权码响应该如 图 1 的步骤 (C) 中所示。如果资源所有者批准了访问请求,授权服务器会发出一个授权码。授权服务器将用户代理重定向到步骤 (A) 中作为请求的一部分的重定向 URI,并将授权码包含为重定向 URI 的查询组件的一部分,这里采用的是 application/x-www-form-urlencoded
格式。
URI 参数如下:
Code
:必选项。由授权服务器生成的授权码。该代码是临时的,并且必须在生成后很快过期。客户不得多次使用授权码。使用相同代码进行的任何进一步请求都应该被授权服务器撤销。授权码被绑定到客户端标识符和重定向 URI。State
:必选项。如果客户端的授权码请求中存在 state
参数,此参数必须设置为与从客户端接收的值完全相同。这对应于 图 1 中的步骤 (D)。客户端采用 application/x-www-form-urlencoded
格式向令牌端点(授权服务器)发出一个请求,如 清单 2 所示。
POST /token HTTP/1.1 Host: server.example.com Authorization:Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom&client_id=c342
访问令牌请求必须设置下列参数:
grant_type
:必选项。该值必须设置为 authorization_code
。client_id
:必选项。客户端 ID。client_secret
:可选项。密码,用于与授权服务器进行身份验证。code
:必选项。从服务器收到的授权码。redirect_uri
:必选项。在步骤 (A) 中发送的完全一样。授权服务器验证该代码和重定向 URI 是有效的。在存在机密性客户端的情况下,授权服务器也使用在其请求的主体或 Authorization 标头中传递的客户端凭据来对客户端进行身份验证。
这对应于 图 1 中的步骤 (E)。如果访问令牌请求是有效的,而且获得了授权,授权服务器会在一个访问令牌响应中返回访问令牌。成功的响应示例如 清单 3 所示。
HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "access_token":"2YotnFZFEjr1zCsicMWpAA", "token_type":"Bearer", "expires_in":3600, "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", "example_parameter":"example_value" }
如果请求无效,或者未经授权,那么授权服务器将会使用代码返回一个相应的错误消息。
这是一个可选步骤,如果客户端请求离线访问,并在访问令牌请求中提供一个 refresh_token
,则可以此阿勇此步骤。访问令牌是暂时性的,通常在一个小时后到期。访问令牌到期后,客户端需要重复身份验证过程,资源所有者需要进行登录,并提供授权,让客户可以再次发出访问令牌请求。
如果客户需要刷新访问令牌,而资源所有者没有位于浏览器上,无法进行登录和身份验证,则客户端可以采用离线访问。客户端可以在发出第一个授权码请求时请求离线访问(参见步骤 (A))。根据这项计划,除了访问令牌之外,授权服务器还会返回刷新令牌。刷新令牌是一个长寿令牌,不会过期,除非明确由资源所有者撤销。每当访问令牌到期时,客户端可以使用刷新令牌来重新生成一个访问令牌,资源所有者无需登录和授权访问请求。
客户端采用 application/x-www-form-urlencoded
格式向令牌端点(授权服务器)发出一个请求,如 清单 4 所示:
POST /token HTTP/1.1 Host: server.example.com Authorization:Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
请求参数的定义如下:
grant_type
:必选项。该值必须设置为 refresh_token
。refresh_token
:必选项。这是之前从访问令牌请求获得的令牌。scope
:可选项。访问请求的范围。授权服务器验证刷新令牌并发出一个新的访问令牌。
如果请求成功,授权服务器将会返回一个新的访问令牌。成功的响应示例如 清单 5 所示。
HTTP/1.1 200 OK Content-Type: application/json;charset=UTF-8 Cache-Control: no-store Pragma: no-cache { "access_token":"2YotnFZFEjr1zCsicMWpAA", "token_type":"Bearer", "expires_in":3600, "example_parameter":"example_value" }
如果请求无效,或者未经授权,那么授权服务器将会使用代码返回一个相应的错误消息。
示例 Outh2.0 客户端是一个动态的 Web 项目。您可以从 下载 部分下载包含项目和源代码的 .war 文件。将 .war 文件导入您的 Eclipse 环境。
您需要安装 Eclipse IDE for Java EE developers,以便设置开发环境,并导入附加项目。您可以从 Eclipse 下载页面 下载 Eclipse。
您需要 Apache Tomcat JSP/Servlet 容器来运行 OAuth 2.0 Web 客户端。您可以从 Apache Tomcat 下载页面 下载 Tomcat。
客户端代码项目取决于以下 JAR 文件:
第 1 至 6 点中提到的 JAR 文件可以在 HttpComponents
JAR 文件中找到,您可以从 HttpComponents Downloads 下载页面 下载该文件。可以从 JSON Simple 项目 下载 json-simple-1.1.1.jar
文件。确保这些 jar 文件被复制到 Apache Tomcat 安装目录的 lib 文件夹中。Tomcat 中默认情况下可能已经提供了一些必要的 JAR 文件,所以您只需复制缺少的文件即可。
在安装了 Eclipse 和 Apache Tomcat 之后,需要导入来自 下载 部分的 .war 文件。为了导入 war 文件,请遵循 Eclipse 网站的 "导入 Web 压缩 (WAR) 文件" 中列出的这些简单步骤。
在将 .war 文件导入到 Eclipse 时,请确保关联了 Tomcat 服务器与您的项目。您需要选择 Tomcat 版本,并提供 Tomcat 安装根目录的路径,以便完成配置。
您可以在 "创建一个 Tomcat 服务器 " 中了解有关如何关联 Tomcat 服务器与项目的更详细说明。
在成功将 .war 文件导入到 Eclipse 工作区后,您会在项目层次中的 Java Resources/src 下找到源代码。
在成功导入 .war 文件并使用必要的 JAR 文件设置 Tomcat 之后,您可以运行客户端:右键单击项目名称 OAuth2.0_AuthCode 并选择 Run As 和 Run on Server。
这会将客户端部署到服务器,并在 Eclipse 内部浏览器中加载 index.html 页面。就个人而言,我更喜欢在外部浏览器上与 Web 客户端进行交互。
您可以从任何浏览器导航至以下网址,访问 Web 客户端: http://localhost:8080/OAuth2.0_AuthCode
。
后续章节会详细解释客户端代码,并告诉您如何使用流行的 OAuth 2.0 兼容的服务器(如 Salesforce、Google 和 IBM)测试这个客户端。
本文中的示例 OAuth 2.0 客户端实现了认证码授权。示例客户端代码是一个 Web 应用程序,而不是一个普通的 Java 项目,后者适用于前面文章中所讨论的授权类型。这是因为,认证码授权流程的目的是满足 Web 应用程序,并针对 Web 浏览器的用户代理进行了优化。
使用在项目的 src 文件夹中提供的 Oauth2Client.config 属性文件向客户端提供所需的输入参数。输入参数是:
scope
:这是一个可选参数。它代表访问请求的范围。由服务器返回的访问令牌只可以访问在 scope 中提到的那些服务。state
:这是一个可选参数,用于维持客户端请求和来自授权服务器的重定向响应之间的状态,目的是确保客户端的完整性。grant_type
:需要将这个参数设置为 authorization_code
,表示认证码授权类型。client_id
:注册应用程序时由资源服务器提供的客户端或使用者 ID。client_secret
:注册应用程序时由资源服务器提供的客户端或使用者的密码。access_token
:令牌端点响应有效的和经过授权的访问令牌请求时所返回的访问令牌。由授权服务器返回的授权代码用于交换访问令牌。refresh_token
:令牌端点响应有效的和经过授权的访问令牌请求时所返回的刷新令牌。刷新令牌可以用于刷新过期的访问令牌,无需资源所有者的存在就可以再次进行身份验证。客户端需要明确地向服务器请求刷新令牌。redirect_uri
:授权服务器将用户代理作为授权码请求的一部分重定向到该 URI。授权码被发送到这个 URI。authentication_server_url
:这表示授权服务器。获取授权码的所有请求都需要被发送到这个 URL。token_endpoint_url
:这表示令牌端点。通过刷新令牌请求来获取访问令牌和刷新访问令牌的所有请求都需要发送到这个 URL。resource_server_url
:这表示需要联系的资源服务器的 URL,通过将 Authorization 标头中的访问令牌传递给它,访问受保护的资源。approval_prompt_key
:授权服务器用于定义授权提示条件的属性名。通常,每个授权服务器(Salesforce、Google、IBM 等)都有一个需要作为授权码请求的一部分传递的自定义属性,用于表示该客户端是否要强制执行每个请求的授权提示。Google 的属性名是 approval_prompt
。对于 Salesforce,它是 login consent
。access_type_key
:授权服务器用于定义访问类型条件的属性名。通常,每个授权服务器(Salesforce、Google、IBM 等)都有一个自定义方法,客户端可以通过它传达指令,比如它想要一个刷新令牌和访问令牌作为访问令牌请求的一部分。Google 通过提供 access_type
属性执行此方法。Salesforce 要求您输入 refresh_token
值作为 scope 的一部分。access_type_value
:access_type_key
属性的值。对于 Google,您需要将 offline
值传递给服务器,以包含刷新令牌和访问令牌。图 2 显示了示例客户端代码的 index.html 页面。在 Eclipse 中成功配置项目并将其部署到 Tomcat 后,您应该看到这个页面。
客户端暴露了您需要了解的有关 OAuth 2.0 的两种最基本操作。首先,客户端显示了如何从服务器获取一个访问令牌。其次,客户端显示了如何使用现有的访问令牌访问来自服务器的受保护资源。
运行客户端:
caller=token (access token request), caller=resource (access protected resource request)
。清单 6 中的客户端代码段选自 OAuth2Client 类的 doGet
方法。
String caller = request.getParameter(OAuthConstants.CALLER); String code = request.getParameter(OAuthConstants.CODE); //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 List<String> invalidProps = OAuthUtils.validateInput(oauthDetails); if(invalidProps!=null && invalidProps.size() == 0){ //Validation successful if(OAuthUtils.isValid(caller)){ //Request was sent from web application. //Check type of request if(caller.equalsIgnoreCase(OAuthConstants.TOKEN)){ //Request for Access token oauthDetails.setAccessTokenRequest(true); String location = OAuthUtils.getAuthorizationCode(oauthDetails); //Send redirect to location returned by endpoint response.sendRedirect(location); return; } else{ //Request for accessing protected resource if(!OAuthUtils.isValid(oauthDetails.getResourceServerUrl())){ invalidProps.add(OAuthConstants.RESOURCE_SERVER_URL); } if(!OAuthUtils.isValid(oauthDetails.getAccessToken())){ if(!OAuthUtils.isValid(oauthDetails.getRefreshToken())){ invalidProps.add(OAuthConstants.REFRESH_TOKEN); } } if(invalidProps.size() > 0){ sendError(response, invalidProps); return; } Map<String,String> map = OAuthUtils.getProtectedResource(oauthDetails); response.getWriter().println(new Gson().toJson(map)); return; } } else if(OAuthUtils.isValid(code)){ //Callback from endpoint with code. Map<String,String> map = OAuthUtils.getAccessToken(oauthDetails, code); response.getWriter().println(new Gson().toJson(map)); return; } else{ //Invalid request/error response String queryString = request.getQueryString(); String error = "Invalid request"; if(OAuthUtils.isValid(queryString)){ //Endpoint returned an error error = queryString; } response.getWriter().println(error); return; } } else{ //Input is not valid.Send error sendError(response, invalidProps); return; }
清单 6 的注意事项:
caller
和 code
。如前面所示,由用户界面发送该请求, caller
参数会有一个有效的值;否则,授权服务器发送该请求作为重定向调用的一部分,而且 code
会有一个有效的值。OAuth2Client.config
文件中提供的属性,创建一个 OAuthDetails
bean。清单 7 中的代码演示了如何发出授权代码请求。
HttpPost post = new HttpPost(oauthDetails.getAuthenticationServerUrl()); String location = null; String state = oauthDetails.getState(); List<BasicNameValuePair> parametersBody = new ArrayList<BasicNameValuePair>(); parametersBody.add(new BasicNameValuePair(OAuthConstants.RESPONSE_TYPE, OAuthConstants.CODE)); parametersBody.add(new BasicNameValuePair(OAuthConstants.CLIENT_ID, oauthDetails.getClientId())); parametersBody.add(new BasicNameValuePair(OAuthConstants.REDIRECT_URI, oauthDetails.getRedirectURI())); if (isValid(oauthDetails.getScope())) { parametersBody.add(new BasicNameValuePair(OAuthConstants.SCOPE, oauthDetails.getScope())); } if (isValid(oauthDetails.getApprovalPromptValue())) { parametersBody.add(new BasicNameValuePair( oauthDetails.getApprovalPromptKey(), oauthDetails .getApprovalPromptValue())); } if (isValid(oauthDetails.getAccessTypeValue())) { parametersBody.add(new BasicNameValuePair( oauthDetails.getAccessTypeKey(), oauthDetails .getAccessTypeValue())); } if (isValid(state)) { parametersBody.add(new BasicNameValuePair(OAuthConstants.STATE, oauthDetails.getState())); } 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(); System.out.println("Code " + code); Map<String, String> map = handleURLEncodedResponse(response); if (OAuthConstants.HTTP_SEND_REDIRECT == code) { location = getHeader(response.getAllHeaders(), OAuthConstants.LOCATION_HEADER); if (location == null) { System.out .println("The endpoint did not pass in valid location header for redirect"); throw new RuntimeException( "The endpoint did not pass in valid location header for redirect"); } } else { System.out .println("Was expecting code 302 from endpoint to indicate redirect.Recieved httpCode " + code); throw new RuntimeException( "Was expecting code 302 from endpoint to indicate redirect.Recieved httpCode " + code); } } catch (ClientProtocolException e) { // TODO Auto-generated catch block e.printStackTrace(); throw new RuntimeException(e.getMessage()); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); throw new RuntimeException(e.getMessage()); } return location;
有关 清单 7 中的代码的注意事项:
HttpPost
方法,它用于发送 URL 编码的参数。response_type
、client_id
和 redirect_uri
是强制性参数,被包括在请求中。state
、scope
、approval_prompt_key/value
和 access_type_key/value
,如果配置文件中提供了它们的有效值,则将它们也包括在内。DefaultHttpClient
向授权服务器发出请求。Location
标头。Location
标头。Location
标头包含客户端重定向用户代理(Web 浏览器)所需的 URI。该 URI 通常是一个登录提示,让资源所有者登录并向客户端提供授权。OAuth2Client.doGet()
)。Oauth2Client.doGet()
方法将响应重定向到位置 URI。code
。清单 8 中的代码显示了使用在上一步中接收到的 code 来发出最终的访问令牌请求。
HttpPost post = new HttpPost(oauthDetails.getTokenEndpointUrl()); String clientId = oauthDetails.getClientId(); String clientSecret = oauthDetails.getClientSecret(); String scope = oauthDetails.getScope(); Map<String, String> map = new HashMap<String, String>(); List<BasicNameValuePair> parametersBody = new ArrayList<BasicNameValuePair>(); parametersBody.add(new BasicNameValuePair(OAuthConstants.GRANT_TYPE, oauthDetails.getGrantType())); parametersBody.add(new BasicNameValuePair(OAuthConstants.CODE, authorizationCode)); parametersBody.add(new BasicNameValuePair(OAuthConstants.CLIENT_ID, clientId)); if (isValid(clientSecret)) { parametersBody.add(new BasicNameValuePair( OAuthConstants.CLIENT_SECRET, clientSecret)); } parametersBody.add(new BasicNameValuePair(OAuthConstants.REDIRECT_URI, oauthDetails.getRedirectURI())); 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(); map = handleResponse(response); accessToken = map.get(OAuthConstants.ACCESS_TOKEN); } catch (ClientProtocolException e) { // TODO Auto-generated catch block e.printStackTrace(); throw new RuntimeException(e.getMessage()); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); throw new RuntimeException(e.getMessage()); } return map;
清单 8 的注意事项:
HttpPost
方法向令牌端点发出访问令牌请求。grant_type
、code
、client_id
和 redirect_uri
是强制性参数。client_secret
是有效的,那么它也会包含在请求中。code
的值是先前的请求中由授权服务器返回的代码。如前所述,访问令牌通常是临时性的,典型的寿命是一个小时。刷新令牌允许客户端自动刷新过期的访问令牌,无需资源所有者登录,并再次对客户端进行身份验证。清单 9 中的代码说明了这一点。
String clientId = oauthDetails.getClientId(); String clientSecret = oauthDetails.getClientSecret(); String scope = oauthDetails.getScope(); String refreshToken = oauthDetails.getRefreshToken(); Map<String, String> map = new HashMap<String, String>(); if (!isValid(refreshToken)) { throw new RuntimeException( "Please provide valid refresh token in config file"); } List<BasicNameValuePair> parametersBody = new ArrayList<BasicNameValuePair>(); parametersBody.add(new BasicNameValuePair(OAuthConstants.GRANT_TYPE, OAuthConstants.REFRESH_TOKEN)); parametersBody.add(new BasicNameValuePair(OAuthConstants.REFRESH_TOKEN, oauthDetails.getRefreshToken())); 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; try { post.setEntity(new UrlEncodedFormEntity(parametersBody, HTTP.UTF_8)); response = client.execute(post); int code = response.getStatusLine().getStatusCode(); map = handleResponse(response); } catch (ClientProtocolException e) { // TODO Auto-generated catch block e.printStackTrace(); throw new RuntimeException(e.getMessage()); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); throw new RuntimeException(e.getMessage()); } return map;
清单 9 的注意事项:
HttpPost
方法向令牌端点发出请求。grant_type
和 refresh_token
参数是强制性的。client_id
、client_secret
和 scope
也被包括在内。清单 10 中的代码演示了如何使用访问令牌来访问受保护的资源。如果访问令牌过期,则刷新访问令牌,然后重试访问受保护的资源。
String resourceURL = oauthDetails.getResourceServerUrl(); Map<String, String> map = new HashMap<String, String>(); HttpGet get = new HttpGet(resourceURL); get.addHeader(OAuthConstants.AUTHORIZATION, getAuthorizationHeaderForAccessToken(oauthDetails .getAccessToken())); DefaultHttpClient client = new DefaultHttpClient(); HttpResponse response = null; String accessToken = null; int code = -1; try { response = client.execute(get); code = response.getStatusLine().getStatusCode(); if (code == OAuthConstants.HTTP_UNAUTHORIZED || code == OAuthConstants.HTTP_FORBIDDEN) { // Access token is invalid or expired.Regenerate the access // token System.out .println("Access token is invalid or expired.Refreshing access token...."); map = refreshAccessToken(oauthDetails); accessToken = map.get(OAuthConstants.ACCESS_TOKEN); 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 refresh access token"); } } map = handleResponse(response); } catch (ClientProtocolException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { get.releaseConnection(); } return map;
清单 10 的注意事项:
OauthDetails
bean。HttpGet
从资源服务器中检索受保护的资源。Authorization:
Bearer accessTokenValue
。DefaultHttpClient
,向资源服务器发出一个 get 请求。OauthDetails
bean 中的访问令牌值。该代码还会使用新的访问令牌值替换 get
方法中现有的 Authorization 标头。Salesforce.com 是一个流行的软件即服务 (SaaS) 应用程序,支持 OAuth 2.0 的认证码授权类型。
在 Salseforce.com 注册:
Login
,然后单击 Sign up for free。password12312123
)。现在,您已经在 Salesforce 设置了 OAuth 2.0 兼容的服务器,您可以测试客户端并从服务器检索受保护的信息。
您可以在 Java resources/src 文件夹下找到 Oauth2Client_salesforce.config 文件。这个配置文件是为 salesforce.com 定制的,可以用作一个提供配置值的模板,以便针对 Salesforce.com 进行测试。
如上文所示,在 Tomcat 服务器上运行 Oauth2Client Web 应用程序,进入测试客户端的开始页面,如 图 3 所示。
图 3 显示了测试客户端页面。单击 Start Test 获取访问令牌。接下来,您会看到 Salesforce 访问令牌请求的登录提示,如 图 4 所示。
图 4 显示了 Salesforce 登录页面,从 Salesforce.com 收到 302 和 Location
标头后,客户端将用户代理(Web浏览器)重定向到该页面。
在登录后,您会看到 Salesforce 访问令牌请求批准提示,如 图 5 所示。
图 5 显示资源所有者的批准提示(登录后),向 VarunOAuth2.0 App 授予访问权限,该应用是请求访问资源所有者的数据的示例代码。
在 Salesforce 批准提示上对客户端授予访问权限之后,令牌端点的响应中包括访问令牌和刷新令牌,以及其他 Salesforce 特定的数据。您应该在 Eclipse 控制台窗口中看到输出,如 清单 11 所示。
********** Response Received ********** id = https://login.salesforce.com/id/00D90000000mQaYEAU/00590000001HCB7AAO issued_at = 1408356835704 scope = full refresh_token instance_url = https://ap1.salesforce.com token_type = Bearer refresh_token = 5Aep8617VFpoP.M.4vPT_5eQXhIJTvFPNyK2GaBz7xFooRQE590MJSZNVqfTXKUqoiZH_yhm_ZpaVsmp id_token = eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjE5MCJ9.eyJleHAiOjE0MDgzNTY5NTUsInN1YiI6Imh0dHBzOi8vbG9naW4uc2FsZXNmb3JjZS5jb20vaWQvMDBEOTAwMDAwMDBtUWFZRUFVLzAwNTkwMDAwMDAxSENCN0FBTyIsImF0X2hhc2giOiJ3eWJpZ1NRTEtaNjdiYlRxWUJxNEVnIiwiYXVkIjoiM01WRzlZNmRfQnRwNHhwNXhLcHgxcFBtcFZidnlUV09Gd2FWbkxwOFpDYjRZVjBRX2FJcHI2WTZVazd1ZHcxcWtCTFZQcVVFNWY2blU1NW5xTWljMCIsImlzcyI6Imh0dHBzOi8vbG9naW4uc2FsZXNmb3JjZS5jb20iLCJpYXQiOjE0MDgzNTY4MzV9.W-r2-k-creIijmRmZ5qQff-bonjjtynNivJAv5XaSGedGhPavWlQpmn76B9k5vB7TWUDTX6y2NroTuIpBi-F2jrO0dunN1giPfv-YKyrCYrpy75J7NXmnSnDGrKhhYgkcR7x9s9MLMoMD-Vf1wsJr58XU2He-UwfrihfkdJzvLKiWRNQfz1gCUwlNSws70AQSqrBfB6MHpLqE7ogG1M3SOp6B4hbcA8NC_zwnvJwIDmF4t3_rf6VsgPuPjV_t4PphBhbrln7sm4b9OMcRRycc8WcCvgBvNPjI37uImciDYUGIP25NEy5sRM3mEU392YlmR5AoHUqOVOqdO9DS1ULWxBy3Q-Fp1wyKYyWiCrUMXe5QdtmeBmkpzptCKXwAhfxhLBdai4bBFfh8K3If4UP-WeNcpMwNkiRhVElwntlqtCPaSs4BLiZGonpLLgwET-f_Iyxs4BauCNvyWDbme_2it2V5AEHgJoKvuf2oU6hD8-Sit8MsdEdc2ugf-nk96QJt5px3QvChfDIE8B7W5trzXagkvzVcXXJV06zJbDUf-ioz7zDTI4Popkxlb31cQiaLAtz2IxIUtjZAfAcTXJaxi8txjV8glqS8Z61145bUaitXgGmZhZAqeefrqLneyCDD--EPNWDIdQYSPhRPbtFb5Aa89AMDNIePxTNnIWShNs access_token = 00D90000000mQaY!AQ8AQMgSI4eZOjvKJGVPuWISCkuS1WC0R6gOWpMg57bMwVWGkyS9_Wraa5ooMrjTfaqMmXmXlgParPk1rjn0vH5BxbNRq3W0 signature = o1VBoC/PzvMw08hZGxYvXLiV/SXQirHBl8qOrk1Mu6Q=
现在,您有了访问令牌,可以发出请求,从 Salesforce 访问资源所有者的帐户信息,该服务器要求使用 OAuth 2.0 进行身份验证。
用访问令牌和刷新令牌更新 Oauth2Client.confg 文件。还要使用您想测试的资源服务器 URL 来设置资源服务器 URL 属性。例如,您可以将其设置为由 Salesforce 作为访问令牌响应的一部分返回的 id
字段。这个 id
字段用于访问资源所有者的帐户信息(例如,https://login.salesforce.com/id/00D90000000mQaYEAU/00590000001HCB7AAO
)。
刷新该项目,并在 Tomcat 服务器上再次运行该项目。单击 Start Test 来访问受保护的资源。
您的 Eclipse 控制台窗口应该显示类似于 清单 12 的输出。
********** Response Received ********** addr_city = null email_verified = true locale = en_US addr_state = Unknown asserted_user = true nick_name = vern.ojha1******** id = https://login.salesforce.com/id/00D9000000QaYEAU/005900001HCB7AAO first_name = varun timezone = America/Los_Angeles username = ******* mobile_phone = null user_id = ************ addr_street = null status = {"created_date":null,"body":null} user_type = STANDARD urls = {"sobjects":"https:////ap1.salesforce.com//services//data//v{version}//sobjects//","feeds":"https:////ap1.salesforce.com//services//data//v{version}//chatter//feeds","users":"https:////ap1.salesforce.com//services//data//v{version}//chatter//users","query":"https:////ap1.salesforce.com//services//data//v{version}//query//","enterprise":"https:////ap1.salesforce.com//services//Soap//c//{version}//00D90000000mQaY","recent":"https:////ap1.salesforce.com//services//data//v{version}//recent//","feed_items":"https:////ap1.salesforce.com//services//data//v{version}//chatter//feed-items","search":"https:////ap1.salesforce.com//services//data//v{version}//search//","partner":"https:////ap1.salesforce.com//services//Soap//u//{version}//00D90000000mQaY","rest":"https:////ap1.salesforce.com//services//data//v{version}//","groups":"https:////ap1.salesforce.com//services//data//v{version}//chatter//groups","metadata":"https:////ap1.salesforce.com//services//Soap//m//{version}//00D90000000mQaY","profile":"https:////ap1.salesforce.com//0000001CB7AAO"} mobile_phone_verified = false is_app_installed = true photos = {"picture":"https:////c.ap1.content.force.com//profilephoto//005//F","thumbnail":"https:////c.ap1.content.force.com//profilephoto//005//T"} display_name = varun ojha last_modified_date = 2013-06-04T07:43:42.000+0000 email = ************** addr_country = IN organization_id = 00D90000000mQaYEAU last_name = ojha utcOffset = -28800000 active = true language = en_US addr_zip = 560071
如您所见,您可以通过使用 OAuth 2.0 进行身份验证,从 Salesforce 成功访问资源所有者的帐户信息。在配置文件中提供的访问令牌过期后,客户端会在下一个请求中自动重新生成访问令牌,并使用它来检索资源服务器 URL 中提供的受保护资源。
Google 使用 OAuth 2.0 对 API 进行身份验证,这些 API 可以用于访问如 GoogleDrive、TaskQueue 和 CloudSQL 等服务。按照以下网址的说明,您可以设置一个应用程序,在 Google 上测试您的 OAuth 2.0 客户端:https://developers.google.com/accounts/docs/OAuth2?hl=ES。
现在,您已经设置了 OAuth 2.0 兼容的服务器,您可以测试客户端,并从服务器检索受保护的信息。
您可以在 Java resources/src 文件夹下找到 Oauth2Client_Google.config 文件。这个配置文件被定制为与 Google 服务结合使用,而且可以用作提供配置值的模板。
在 Tomcat 服务器上运行 Oauth2Client Web 应用,如前所示。获取访问令牌操作和访问受保护资源操作的流程和输出如 图 6 所示。
图 6 显示了 Google 登录页面,从 Google 收到 302 和 Location
标头后,客户端将用户代理(Web浏览器)重定向到该页面。
图 7 显示了资源所有者的批准提示(登录后),向 Cast Iron App 授予访问权限,该应用请求访问资源所有者的数据。
资源所有者对客户端授予访问权限后,令牌端点的响应中包含访问令牌和刷新令牌。您应该在控制台窗口中看到类似于 清单 13 所示的输出。
********** Response Received ********** expires_in = 3600 token_type = Bearer refresh_token = 1/TtCxaFlKMRsHeIlxrY-2ZJIO8DcRmQEiQ_2Wxw8 access_token = ya29.ZQDpI-ahF6TMURwAAABqBu-2-U0_lUWfbwh053j3db3PzaNXV4k_k6fc_VT7uQ
现在,您有了访问令牌,可以发出请求,访问 GoogleDrive 信息,这要求使用 OAuth 2.0 进行身份验证。
用访问令牌和刷新令牌更新 Oauth2Client.confg 文件,并使用您希望对其进行测试的资源服务器 URL 来填充资源服务器 url
属性。我要用 https://www.googleapis.com/drive/v2/about 替换它,这是 GoogleDrive 帐户信息 URL。
现在,刷新该项目,并在 Tomcat 服务器上再次运行它。单击 Start Test 来访问受保护的资源。
您应该在控制台窗口中看到类似于 清单 14 的片段。
********** Response Received ********** name = Varun Ojha features = [{"featureName":"ocr"},{"featureName":"translation","featureRate":0.5}] quotaBytesTotal = 16106127360 largestChangeId = 1911 rootFolderId = 0ALupA2Opqyp1Uk9PVA quotaType = LIMITED quotaBytesByService = [{"bytesUsed":"229","serviceName":"DRIVE"},{"bytesUsed":"1154860956","serviceName":"GMAIL"},{"bytesUsed":"568174452","serviceName":"PHOTOS"}] permissionId = 01822620516456565194 quotaBytesUsedInTrash = 119332
如您所见,您可以通过使用 OAuth 2.0 进行身份验证,从 GoogleDrive 成功访问资源所有者的帐户信息。
在配置文件中提供的访问令牌过期后,客户端会在下一个请求中自动重新生成访问令牌,并使用重新生成的访问令牌来检索资源服务器 URL 中提供的受保护资源。
客户端示例代码已经用 OAuth 2.0 兼容的 IBM 端点(比如 IBM Websphere® Application Server 和 IBM DataPower)成功完成了测试。®
关于在 Websphere Application Server 上设置 OAuth 2.0 服务器的说明,请参阅 "使用 OAuth:在 WebSphere Application Server 上启用 OAuth 服务提供程序"。
可以采用与在 Salesforce 和 Google 上进行测试的相同方法对 IBM 端点进行测试。
本教程系列的第 3 部分解释了认证码授权类型的基础知识。本文演示了如何在 Java 代码中编写一个通用的 OAuth 2.0 客户端,以便连接到 OAuth 2.0 兼容的多个端点,从中获取受保护的资源。来自 下载 的示例客户端 Web 项目可以导入到 Eclipse 环境中,您可以将此作为一个起点,创建一个定制的 Oauth 2.0 客户端。
描述 | 名字 | 大小 |
---|---|---|
Sample Java code for Authorization code grant type | OAuth2.0_AuthCode_part_3.war | 25KB |