OAuth是一种开放的授权标准,可让客户端代表资源所有者访问受保护的服务器资源。 资源所有者可以是其他客户端或最终用户。 OAuth还可以帮助最终用户授权第三方访问其服务器资源,而无需共享其凭据(例如用户名和密码)。 本系列文章遵循RFC6749中概述的OAuth 2.0授权框架。 可以在Internet工程任务组网站上找到RFC 6749中概述的完整OAuth 2.0授权框架。
授权补助
授权授予是一个凭证,代表可用于访问受保护资源的资源所有者的授权。 客户端使用此凭据来获取访问令牌,并且最终将该访问令牌与访问受保护资源的请求一起发送。 OAuth 2.0定义了四种授权类型:
- 授权码
- 隐含的
- 资源所有者密码凭证
- 客户凭证
这个由四部分组成的系列文章将指导您使用上面列出的每种授权类型,在Java™编程中实现OAuth 2.0客户端。 在第二部分中,我将解释如何实现客户端凭据授予。 本文详细介绍了该授权,并说明了示例客户端代码,您可以使用这些示例客户端代码与支持该授权的任何OAuth 2.0兼容服务器进行接口。 在本文末尾,您应该对客户端实现有一个完整的了解,并准备下载示例客户端代码以进行自己的测试。
客户凭证授予
在此授予中,机密客户端可以仅使用其客户端凭据(或其他受支持的身份验证方式,例如公用/专用密钥对)从授权服务器请求访问令牌。 假定客户端正在请求访问受其自身控制的受保护资源(客户端是资源所有者)。
图1所示的流程包括以下步骤:
(A)OAuth 2.0客户端使用其客户端凭据向授权服务器进行身份验证,并从令牌端点请求访问令牌
(B)授权服务器对OAuth 2.0客户端进行身份验证并验证客户端凭据。 如果有效,授权服务器将发出访问令牌。
图1.客户端凭证流
访问令牌请求
访问令牌请求对应于步骤A,如图1所示。
客户端使用通过application/x-www-form-urlencoded
格式发送的以下参数向令牌端点(授权服务器)发出请求。
-
grant_type
:必需。 值必须设置为“client_credentials
” -
client_id
:必需。 客户编号。 -
client_secret
:必需。 客户机密/密码。 -
scope
:可选。 访问请求的范围
由于客户端身份验证被用作授权授予,因此不需要其他授权。 例如,客户端使用传输层安全性发出以下HTTP请求:
清单1.客户端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
访问令牌响应
如图1所示,访问令牌响应与步骤B对应。如果访问令牌请求有效且被授权,则授权服务器返回访问令牌。 清单2显示了成功的响应。
清单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客户端。 该代码以Java项目的形式组织,可以将其导入到Eclipse环境中。
先决条件
您将需要Java EE开发人员的Eclipse IDE来设置您的开发环境并导入附加的项目。 从Eclipse下载页面下载Eclipse。
该项目取决于以下JAR文件:
- commons-codec-1.6.jar
- commons-logging-1.1.1.jar
- httpclient-4.2.5.jar
- httpclient-cache-4.2.5.jar
- httpcore-4.2.4.jar
- httpmime-4.2.5.jar
- json-simple-1.1.1.jar
可以在HttpComponents JAR文件中找到第1至6点提到的JAR文件。 可以从Apache HTTP Component项目下载。 可以从Simple JSON project page下载json-simple-1.1.1.jar文件。 确保将这些JAR文件复制到Java项目的lib
文件夹中。
OAuth 2.0客户端代码
此处讨论的OAuth 2.0客户端实现了客户端凭据授予。 在本教程系列的后续部分中,将讨论其余的授权类型,并将更新客户端代码。
输入参数
客户端的输入参数需要通过Oauth2Client.config属性文件提供,该文件位于项目的资源文件夹下。
-
scope
:这是一个可选参数。 它表示访问请求的范围。 服务器返回的访问令牌只能访问范围中提到的那些服务。 -
grant_type
:这需要设置为代表客户端证书授予的client_credentials
。 -
client_id
:资源服务器向您注册应用程序时提供的客户端或使用者ID。 -
client_secret
:资源服务器向您注册应用程序时提供的客户端或使用者密码。 -
access_token
:授权服务器响应有效和授权的访问令牌请求而返回的访问令牌。 作为此请求的一部分,将交换您的客户端凭据以获得访问令牌。 -
authentication_server_url
:这表示令牌端点。 所有授予和重新生成访问令牌的请求都必须发送到此URL。 -
resource_server_url
:这表示通过将授权标头中的访问令牌传递给受保护资源来访问该资源服务器所需要的URL。
客户端代码如清单3所示。
清单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中的代码演示了如何使用访问令牌访问受保护的资源。
清单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
请求。 - 如果从资源服务器收到的响应代码是401,则用于身份验证的访问令牌可能已过期或无效。
- 下一步是重新生成访问令牌。 (请参见清单5。)
- 成功重新生成访问令牌之后,请更新
OauthDetails
bean中的访问令牌值。 用新的访问令牌值替换get
方法中现有的Authorization标头。 - 现在再次发出访问受保护资源的请求。
- 如果访问令牌有效且资源服务器URL正确,则您应该能够在控制台中看到响应的内容。
重新生成过期的访问令牌
清单5中的代码处理过期访问令牌的再生。
清单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。 - Post请求将
client_id
,client_secret
和scope
(可选)作为URL编码参数发送为有效内容的一部分。 - 根据OAuth 2.0授权框架,客户端应在发出访问令牌请求时使用客户端凭据或服务器提供的用于身份验证的任何其他凭据来设置Authorization标头。 但这取决于授权服务器的实现。 客户端代码发出初始请求,而不添加基本身份验证标头。 如果服务器返回未经授权的响应,则客户端随后将通过使用客户端凭据进行身份验证进行尝试。
- OAuth 2.0规定访问令牌响应以JSON格式发送。 但是为了灵活性,我添加了实用程序方法来处理来自服务器的XML或URL编码的响应。
测试OAuth 2.0客户端
本部分说明了如何设置兼容OAuth 2.0的端点,并针对该端点测试客户端。
用端点测试客户端
此客户端已通过Twitter和OAuth 2.0兼容的IBM端点(例如IBM®Websphere®Application Server和IBM DataPower™)成功测试。
在“ 在WebSphere Application Server中启用OAuth服务提供程序 ”中可以找到在Websphere应用程序服务器上设置OAuth 2.0端点的说明。
运行客户端
既然您已经设置了符合OAuth 2.0的服务器,就可以测试客户端并从服务器中检索受保护的信息了。
- 将本教程附带的Java项目导入到Eclipse工作区中。
- 下载依赖项JAR文件并将其复制到项目的lib文件夹中。
- 导航到resources / com / ibm / oauth / Oauth2Client.config文件,并填写
client_id
,client_secret
和authorization_server
URL的值。 - 打开
Oauth2Client.java
并在Eclipse中运行它。
访问令牌输出
您应该在控制台窗口中看到输出,如清单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
从服务器检索用户信息
有了访问令牌后,就可以向托管在需要OAuth 2.0身份验证的WebSphere Application Server上的Web应用程序发出请求。
- 使用访问令牌更新Oauth2Client.confg文件,并使用要测试的资源服务器URL填充资源服务器URL属性。
- 再次运行Oauth2Client.java。
您应该在控制台窗口中看到输出,如清单7所示。
清单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授权框架中概述的其余两种授权类型。
翻译自: https://www.ibm.com/developerworks/java/library/se-oauthjavapt2/index.html