原文出处: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
新闻热点
疑难解答