内容


添加 OAuth 认证支持到 HttpClient

简介

HTTP 是众所周知的 Internet 协议。尽管 java.net 包提供基本的通过 HTTP 协议访问资源的功能,但是它既不够灵活,功能也不是足够强大,不能满足现代 web 应用程序的需求。Apache Jakarta Commons HttpClient 使用最新的 HTTP 标准和规范,通过在客户端提供一个高效且功能丰富的包实现来填补这一空白。HttpClient 也被开源项目和商业软件产品广泛采用。

在本文中,学习如何扩展 HttpClient 认证模型。使用第三方 OAuth 库来添加 OAuth 认证。本文还讨论了 HttpClient v3.0.x 和 v4.x 之间的差异。

认证机制

HttpClient 处理服务器认证几乎是透明的。只需要提供登录证书。但是,不同版本有所差异。

HttpClient v3.x
证书存储在 HttpState 实例中,可以使用 setCredentials(AuthScope authscope, Credentials cred)getCredentials(AuthScope authscope) 方法设置或检索。建立在 HttpClient 上的自动授权可以禁用,使用 HttpMethod 类的 setDoAuthentication(boolean doAuthentication) 方法。更改只影响那个方法实例。

也支持先占式(Preemptive)基础认证功能,通过设置 setAuthenticationPreemptive(Boolean preemptive) 实现,但是 支持先占式基础服务。

HttpClient v4.x
您应该实例化 CredentialProvider 来维护用户证书集,并为一个特定认证范围提供证书。通过将 CredentialProvider 添加到 HttpContext,这表示一个 HTTP 流程的执行状态,HttpClient 能够根据主机名、端口号和范围自动进行认证。

先占式认证不再以开箱即用的方式提供,滥用先占式认证可能引起用户证书泄漏。然而,如果您想要一个先占式认证,可以使用一个标准 HttpClient 扩展(比如协议拦截器)启用。

OAuth

OAuth 是一个开放的协议,以一种简单且标准的方法支持来自桌面和 web 应用程序的安全 API 授权。有了 OAuth,一个资源所有者可以授权第三方应用程序访问受保护的资源,而不损害用户证书。(OAuth 协议于 2007 年 10 月在版本 1.0 中确定,在 2009 年 6 月(修订版 A)进行改进。OAuth 2.0 规范正在开发之中。)

图 1 显示了典型的 three-legged OAuth dance。

图 1. Three-legged OAuth dance
图表概括显示下述 Browser、Consumer 和 Service Provider 的 10 个步骤。
图表概括显示下述 Browser、Consumer 和 Service Provider 的 10 个步骤。

依照上图编号,当 OAuth three-legged 握手启动时:

  1. 客户为 OAuth 握手请求一个临时令牌。这个令牌用于维护握手会话。
  2. 确认了客户之后,服务器提供商颁发一个短期请求令牌。
  3. 客户发送一个 HTTP 重定向响应用户浏览器,然后将用户引导到服务供应商进行授权。
  4. 用户检查授权请求,并在服务提供商网站上授予客户访问权限(如果他信任该客户)。
  5. 服务器供应商确定授权,然后发送一个 HTTP 重定向来响应用户浏览器。
  6. 用户浏览器被重定向到客户回调 URL,在这里客户可以完成握手的其余部分。
  7. 客户使用上一步传递的验证器从服务提供商请求访问令牌。
  8. 成功确认之后,服务提供商颁发访问令牌来访问受保护资源。
  9. OAuth 握手完成之后,访问令牌颁发,客户可以使用这个访问令牌代表用户访问受保护的数据。
  10. 服务提供商验证每个到来的 OAuth 请求,如果客户被授权,就返回受保护资源。

当保护的 OAuth 资源被请求后,通常,客户端将得到一个 HTTP 401 的响应,包含一个 WWW-Authentication 头部:

WWW-Authenticate: OAuth realm=<your_realm>

WWW-Authentication 头部指出保护资源的认证模式。然后,HttpClient 可以根据 WWW-Authenticate 头部执行 OAuth 认证。

默认情况下,在 HttpClient 中仅支持基础认证、摘要认证和 NTLM 认证。下一小节 介绍如何在 HttpClient 模式下添加和使用 OAuth 认证。

添加一个自定义的认证模式

除了原生支持基础、摘要和 NTLM 认证之外,HttpClient 有一个机制来插入自定义的额外认证模式,使用 AuthScheme 接口。要使用一个定制的认证模式: 

  1. 实现 AuthScheme 接口。
  2. 使用 AuthPolicy.registerAuthScheme() 注册自定义的 AuthScheme 。
  3. 在 AuthPolicy.AUTH_SCHEME_PRIORITY 首选项中包含这个自定义的 AuthScheme(见本文后面的 HttpClient 3.0.x OAuth 支持)。

使用 oauth.net 的 Java 库来扩展 HttpClient 4.0.1

Oauth.net 提供一个开源 Java™ 库。如上所述,要使用自定义的认证,您需要提供您自己的 AuthScheme 和证书类。OAuth 库已经实现了它自己的 OAuthSchemeOAuthSchemeFactoryOAuthCredentials。您可以利用它们来添加 OAuth 支持到您的 HttpClient 应用程序。

要启用 HttpClient 4.0.1 的一个 OAuth 认证模式:

  1. 在 HttpClient 中注册这个新的 OAuth 认证机制。
  2. 提供一个新 OAuthCredentials
  3. HttpClient 使用一个有序偏好来选择正确的认证机制。您可以使用本地 HttpContext 对象来在请求执行之前定制 HTTP 认证内容,或者您可以在请求执行之后检查其状态。通过设置 HttpContext 对象的 http.auth.scheme-pref 属性修改认证默认首选项。

清单 1 显示了一个示例。注意代码中获取您自己的 OAuthAccessor 的方法被省略了,因为它由您的具体实现而定。

清单 1. 启用 HttpClient 4.0.1 的一个 OAuth 认证模式
AbstractHttpClient httpClient = new DefaultHttpClient();
//register the OAuthScheme
httpClient.getAuthSchemes().register(OAuthSchemeFactory.SCHEME_NAME, 
new OAuthSchemeFactory()); 
//get the OAuthAccessor object
OAuthAccessor accessor = yourMethodToGetOAuthAccessor();
//set credentials 
httpClient.getCredentialsProvider().setCredentials(
new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM,
OAuthSchemeFactory.SCHEME_NAME),
new OAuthCredentials(accessor));
//Adjust the authentication scheme selection
HttpContext localContext = new BasicHttpContext();
localContext.setAttribute("http.auth.scheme-pref", Arrays.asList(new String[] {
"oauth", "digest", "basic"
}));

示例:使用 OAuth 从 LinkedIn 获取配置文件

很多 web 网站支持 OAuth,比如 LinkedIn。以下示例展示如何使用有 OAuth 支持的 HttpClient 从 LinkedIn 获取一个用户的配置文件。

  1. 您需要一个 LinkedIn 账户,需要创建一个应用程序。然后您可以获取 API Key,这在 OAuth 术语中称为 Consumer Key。图 2 显示了一个例子。
    图 2. 获取您应用程序的 Consumer Key 和 Secret
    显示应用程序创建成功消息的屏幕截图,有应用程序详细说明,包含 Company、Application 名称、API Key 和 Secret Key。
    显示应用程序创建成功消息的屏幕截图,有应用程序详细说明,包含 Company、Application 名称、API Key 和 Secret Key。
  2. 有了 Consumer Key 和 Secret Key 之后,向 LinkedIn 发出一个请求来获取 AccessToken 和 Secret,使用以下内容:
    • LinkedIn Oauth 端点 URL
    • 根路径:https://api.linkedin.com
    • 请求令牌路径:/uas/oauth/requestToken
    • 访问令牌路径:/uas/oauth/accessToken
    • 授权路径:/uas/oauth/authorize

    清单 2 是样例代码。

    清单 2. 从 LinkedIn 获取 AccessTokenSecret
    String baseURL = "https://api.linkedin.com";
    String requestTokenURL = baseURL + "/requestToken";
    String authorizationURL = baseURL + "/authorize";
    String accessTokenURL = baseURL + "/accessToken";
    
    String consumerKey="hP80ApmoJkO-9ZHuXC97olUzD1egVI75zKoff9SCKFFTY9zjc
       vWRRRbiNrWbcKIX";
    String consumerSecret="toAk3oV1wKuon9W51lfELLHtZSxBZHih-qMyeDIBrIB2Y1hCASbpmK313
       Wubmrd2";
    OAuthServiceProvider provider = new OAuthServiceProvider(
    				requestTokenURL, authorizationURL, accessTokenURL);
    OAuthConsumer consumer = new OAuthConsumer(
    		"DemoOAuth", consumerKey, consumerSecret, provider);
    OAuthAccessor accessor = new OAuthAccessor(consumer);
    OAuthClient client = new OAuthClient(new HttpClient4());
    List<Parameter> parameters = new ArrayList<OAuth.Parameter>();
    parameters.add(new Parameter("oauth_callback", "yourAppcallbackurl"));
    OAuthMessage msg = client.getRequestTokenResponse(accessor,
    					"POST", parameters);
    String requestToken = msg.getParameter(OAuth.OAUTH_TOKEN);
    String requestSecret = msg.getParameter(OAuth.OAUTH_TOKEN_SECRET);

    现在您有了 URL:

    authorizationURL + "?" + OAuth.OAUTH_TOKEN+ "=" + requestToken

    如果在一个 web 应用程序中,用户应该访问 LinkedIn 或被重定向到 LinkedIn 以得到批准,如下所示。

    图 3. 用户批准
    显示授权 OAuth 访问您的 LinkedIn 账户的过程的屏幕截图
    显示授权 OAuth 访问您的 LinkedIn 账户的过程的屏幕截图

    在您自己的回调 servlet 中,使用清单 3 中的代码获取验证器,然后使用它来请求 AccessTokenSecret

    清单 3. 请求 AccessTokenSecret
    OAuthMessage msg = OAuthServlet.getMessage(request, null);
    String requestToken = msg.getParameter(OAuth.OAUTH_TOKEN);
    String verifier = msg.getParameter(OAuth.OAUTH_VERIFIER);
    …
    get the accessor object in List 1…
    ….
    OAuthClient oauthClient = new OAuthClient(new HttpClient4());
    List<Parameter> list = new ArrayList<Parameter>();
    list.add(new Parameter(OAuth.OAUTH_VERIFIER, verifier));
    OAuthMessage returned = oauthClient.getAccessToken(accessor,
           "POST", list);
    String accessToken = returned.getParameter(OAuth.OAUTH_TOKEN));
    String accessKey = returned.getParameter(OAuth.OAUTH_TOKEN_SECRET));
  3. 添加 OAuth 支持到 HttpClient。

    使用上述代码,注册 OAuthScheme。使用 accessor 对像设置证书。

  4. 使用 OAuth 认证发送请求到 LinkedIn。
    清单 4. 使用 OAuth 认证发送请求到 LinkedIn
    HttpGet httpget = new HttpGet("https://api.linkedin.com/v1/people/~");
    //Run the http get method under the modified context
    httpClient.execute(httpget, localContext);

    现在您可以使用 OAuth 认证通过 HttpClient 获取用户配置文件,如清单 5 所示。

    清单 5. LinkedIn 返回用户信息
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <person>
      <first-name>Zheng</first-name>
      <last-name>BI</last-name>
      <headline>SE at IBM</headline>
      <site-standard-profile-request>
    <url>http://www.linkedin.com/profile?viewProfile=&amp;key=84546207
    &amp;authToken=SMl9&amp;authType=name&amp;
    trk=api*a113393*s121886*</url>
      </site-standard-profile-request>
    </person>

先占式模式支持

HttpClient 不支持开箱即用的先占式认证,但是您可以使用一个协议拦截器事先引入一个 AuthScheme 实例到执行上下文。这个拦截器必须在标准认证拦截器之前 添加到协议处理链。

仅支持 OAuth 1.0 的 web 网站不提供 “挑战” 响应。要使用 OAuth 认证,您需要使用先占式认证。OAuth 库也使用 HttpClient 4.0.1 的 HttpRequestInterceptor 使其得以实现。清单 6 中显示的样例代码可以启用先占式认证。

清单 6. 启用先占式认证
HttpRequestInterceptor preemptiveAuth = new PreemptiveAuthorizer();
httpClient.addRequestInterceptor(preemptiveAuth, 0);

HttpClient 3.0.x OAuth 支持

HttpClient 原生支持基础认证、摘要认证和 NTLM 认证。在 HttpClient 3.0.x 中添加一个自定义的 AuthScheme 与在 4.0.1 中添加有所不同。

  1. OAuth Java Lib 不提供 HttpClient 3.0.x 支持,因此您需要创建您自己的 Scheme 类,来实现 AuthScheme 接口。关于编写该函数的解释不在本文讨论范围中。
  2. 使用 AuthPolicy.registerAuthScheme() 注册 OAuthScheme(见 http://hc.apache.org/httpclient-3.x/apidocs/org/apache/commons/httpclient/auth/AuthPolicy.html#registerAuthScheme%28java.lang.String,%20java.lang.Class%29)
  3. 更改 AuthPolicy.AUTH_SCHEME_PRIORITY 首选项来启用自定义的 AuthScheme,如下所示。
    清单 7. 启用自定义的 AuthScheme
    HttpClient client = new HttpClient();
    List authPrefs = new ArrayList(2);
    authPrefs.add(AuthPolicy.OAUTH);
    authPrefs.add(AuthPolicy.DIGEST);
    authPrefs.add(AuthPolicy.BASIC);
    client.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);

v3.0.x 的局限性

HttpClient 3.0.x 也支持先占式认证,如下所示。

清单 8. HttpClient 3.0.x 支持先占式认证
client.getParams().setAuthenticationPreemptive(true);

然而,在该模式下,您只能使用基础认证。对于一个需要 OAuth 先占式认证的网站,您不能使用 HttpClient 3.0.x。

结束语

HttpClient 认证模式提供一种机制来进行自身扩展,对于在开发期间使用 HttpClient 的应用程序来说,利用一个第三方 OAuth 库来添加 OAuth 认证是比较容易的。然而对于 HttpClient 3.0.x 有一些限制。


相关主题

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=777686
ArticleTitle=添加 OAuth 认证支持到 HttpClient
publish-date=12012011