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
依照上图编号,当 OAuth three-legged 握手启动时:
- 客户为 OAuth 握手请求一个临时令牌。这个令牌用于维护握手会话。
- 确认了客户之后,服务器提供商颁发一个短期请求令牌。
- 客户发送一个 HTTP 重定向响应用户浏览器,然后将用户引导到服务供应商进行授权。
- 用户检查授权请求,并在服务提供商网站上授予客户访问权限(如果他信任该客户)。
- 服务器供应商确定授权,然后发送一个 HTTP 重定向来响应用户浏览器。
- 用户浏览器被重定向到客户回调 URL,在这里客户可以完成握手的其余部分。
- 客户使用上一步传递的验证器从服务提供商请求访问令牌。
- 成功确认之后,服务提供商颁发访问令牌来访问受保护资源。
- OAuth 握手完成之后,访问令牌颁发,客户可以使用这个访问令牌代表用户访问受保护的数据。
- 服务提供商验证每个到来的 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 接口。要使用一个定制的认证模式:
- 实现 AuthScheme 接口。
- 使用 AuthPolicy.registerAuthScheme() 注册自定义的 AuthScheme 。
- 在 AuthPolicy.AUTH_SCHEME_PRIORITY 首选项中包含这个自定义的 AuthScheme(见本文后面的 HttpClient 3.0.x OAuth 支持)。
回页首
使用 oauth.net 的 Java 库来扩展 HttpClient 4.0.1
Oauth.net 提供一个开源 Java? 库。如上所述,要使用自定义的认证,您需要提供您自己的 AuthScheme 和证书类。OAuth 库已经实现了它自己的 OAuthScheme
、OAuthSchemeFactory
和 OAuthCredentials
。您可以利用它们来添加 OAuth 支持到您的 HttpClient 应用程序。
要启用 HttpClient 4.0.1 的一个 OAuth 认证模式:
- 在 HttpClient 中注册这个新的 OAuth 认证机制。
- 提供一个新
OAuthCredentials
。
- 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 获取一个用户的配置文件。
- 您需要一个 LinkedIn 账户,需要创建一个应用程序。然后您可以获取 API Key,这在 OAuth 术语中称为 Consumer Key。图 2 显示了一个例子。
图 2. 获取您应用程序的 Consumer Key 和 Secret
- 有了 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 获取 AccessToken
和 Secret
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. 用户批准
在您自己的回调 servlet 中,使用清单 3 中的代码获取验证器,然后使用它来请求 AccessToken
和 Secret
。
清单 3. 请求 AccessToken
和 Secret
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));
|
- 添加 OAuth 支持到 HttpClient。
使用上述代码,注册 OAuthScheme
。使用 accessor
对像设置证书。
- 使用 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=&key=84546207
&authToken=SMl9&authType=name&
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 中添加有所不同。
- OAuth Java Lib 不提供 HttpClient 3.0.x 支持,因此您需要创建您自己的
Scheme
类,来实现 AuthScheme 接口。关于编写该函数的解释不在本文讨论范围中。
- 使用
AuthPolicy.registerAuthScheme()
注册 OAuthScheme(见 http://hc./httpclient-3.x/apidocs/org/apache/commons/httpclient/auth/AuthPolicy.html#registerAuthScheme%28java.lang.String,%20java.lang.Class%29)。
- 更改
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 有一些限制。