要实现OAuth服务端,就得先理解客户端的调用流程,服务提供商实现可能也有些区别,实现OAuth服务端的方式很多,具体可能看http://oauth.net/code/
各语言的实现有(我使用了Apache Oltu):
- Java
- Apache Oltu
- Spring Security for OAuth
- Apis Authorization Server (v2-31)
- Restlet Framework (draft 30)
- Apache CXF
- NodeJS
- NodeJS OAuth 2.0 Provider
- Mozilla Firefox Accounts. A full stack Identy Provider system developed to support Firefox market place and other services
- Ruby
- Ruby OAuth2 Server (draft 18)
- .NET
- .NET DotNetOpenAuth
- Thinktecture IdentityServer
实现主要涉及参数配置如下:
授权码设置(code)
第三方通过code进行获取 access_token的时候需要用到,code的超时时间为10分钟,一个code只能成功换取一次access_token即失效。
授权作用域(scope)
作用域代表用户授权给第三方的接口权限,第三方应用需要向服务端申请使用相应scope的权限后,经过用户授权,获取到相应access_token后方可对接口进行调用。
令牌有效期(access_token)
access_token是调用授权关系接口的调用凭证,由于access_token有效期(目前为2个小时)较短,当access_token超时后,可以使用refresh_token进行刷新,access_token刷新结果有两种:
1. 若access_token已超时,那么进行refresh_token会获取一个新的access_token,新的超时时间;
2. 若access_token未超时,那么进行refresh_token不会改变access_token,但超时时间会刷新,相当于续期access_token。
refresh_token拥有较长的有效期(30天),当refresh_token失效的后,需要用户重新授权。
项目结构如下:
AuthzController:获取授权码
TokenController:获得令牌
ResourceController:资源服务
ClientController:客户端
基础代码放到github:https://github.com/zhouyongtao/homeinns-web
确保项目8080端口运行,可以手动调试
获得授权码
http://localhost:8080/oauth2/authorize?client_id=fbed1d1b4b1449daa4bc49397cbe2350&response_type=code&redirect_uri=http://localhost:8080/oauth_callback
获得令牌(POST)
http://localhost:8080/oauth2/access_token?client_id=fbed1d1b4b1449daa4bc49397cbe2350&client_secret=fbed1d1b4b1449daa4bc49397cbe2350&grant_type=authorization_code&redirect_uri=http://localhost:8080/oauth_callback&code={code}
也可以使用如下客户端测试代码,访问 http://localhost:8080/client 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
* Created by Irving on 2014/11/24.
* OAuth2 客户端实现
*/
@Controller
@RequestMapping(
"/client"
)
public
class
ClientController {
private
static
Logger logger = LoggerFactory.getLogger(ClientController.
class
);
/*
response_type:表示授权类型,必选项,此处的值固定为"code"
client_id:表示客户端的ID,必选项
redirect_uri:表示重定向URI,可选项
scope:表示申请的权限范围,可选项
state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值
*/
/**
* 获得授权码
* @return
*/
@RequestMapping(method = RequestMethod.GET)
public
String client() {
try
{
OAuthClientRequest oauthResponse = OAuthClientRequest
.authorizationLocation(ConstantKey.OAUTH_CLIENT_AUTHORIZE)
.setResponseType(OAuth.OAUTH_CODE)
.setClientId(ConstantKey.OAUTH_CLIENT_ID)
.setRedirectURI(ConstantKey.OAUTH_CLIENT_CALLBACK)
.setScope(ConstantKey.OAUTH_CLIENT_SCOPE)
.buildQueryMessage();
return
"redirect:"
+oauthResponse.getLocationUri();
}
catch
(OAuthSystemException e) {
e.printStackTrace();
}
return
"redirect:/home"
;
}
/*
grant_type:表示使用的授权模式,必选项,此处的值固定为"authorization_code"
code:表示上一步获得的授权码,必选项。
redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致
client_id:表示客户端ID,必选项
*/
/**
* 获得令牌
* @return oauth_callback?code=1234
*/
@RequestMapping(value =
"/oauth_callback"
,method = RequestMethod.GET)
public
String getToken(HttpServletRequest request,Model model) throws OAuthProblemException {
OAuthAuthzResponse oauthAuthzResponse =
null
;
try
{
oauthAuthzResponse = OAuthAuthzResponse.oauthCodeAuthzResponse(request);
String code = oauthAuthzResponse.getCode();
OAuthClientRequest oauthClientRequest = OAuthClientRequest
.tokenLocation(ConstantKey.OAUTH_CLIENT_ACCESS_TOKEN)
.setGrantType(GrantType.AUTHORIZATION_CODE)
.setClientId(ConstantKey.OAUTH_CLIENT_ID)
.setClientSecret(ConstantKey.OAUTH_CLIENT_SECRET)
.setRedirectURI(ConstantKey.OAUTH_CLIENT_CALLBACK)
.setCode(code)
.buildQueryMessage();
OAuthClient oAuthClient =
new
OAuthClient(
new
URLConnectionClient());
//Facebook is not fully compatible with OAuth 2.0 draft 10, access token response is
//application/x-www-form-urlencoded, not json encoded so we use dedicated response class for that
//Custom response classes are an easy way to deal with oauth providers that introduce modifications to
//OAuth 2.0 specification
//获取access token
OAuthJSONAccessTokenResponse oAuthResponse = oAuthClient.accessToken(oauthClientRequest, OAuth.HttpMethod.POST);
String accessToken = oAuthResponse.getAccessToken();
String refreshToken= oAuthResponse.getRefreshToken();
Long expiresIn = oAuthResponse.getExpiresIn();
//获得资源服务
OAuthClientRequest bearerClientRequest =
new
OAuthBearerClientRequest(ConstantKey.OAUTH_CLIENT_GET_RESOURCE)
.setAccessToken(accessToken).buildQueryMessage();
OAuthResourceResponse resourceResponse = oAuthClient.resource(bearerClientRequest, OAuth.HttpMethod.GET, OAuthResourceResponse.
class
);
String resBody = resourceResponse.getBody();
logger.info(
"accessToken: "
+accessToken +
" refreshToken: "
+refreshToken +
" expiresIn: "
+expiresIn +
" resBody: "
+resBody);
model.addAttribute(
"accessToken"
,
"accessToken: "
+accessToken +
" resBody: "
+resBody);
return
"oauth2/token"
;
}
catch
(OAuthSystemException ex) {
logger.error(
"getToken OAuthSystemException : "
+ ex.getMessage());
model.addAttribute(
"errorMsg"
, ex.getMessage());
return
"/oauth2/error"
;
}
}
}
|
Refer:
https://cwiki.apache.org/confluence/display/OLTU/Index
https://open.weixin.qq.com/cgi-bin/readtemplate?t=resource/app_wx_login_tmpl&lang=zh_CN#faq