oltu是一个开源的oauth2.0协议的实现,本人在此开源项目的基础上进行修改,实现一个自定义的oauth2.0模块。java
关于oltu的使用你们能够看这里:http://oltu.apache.org/git
项目能够从这里下载:http://mirror.bit.edu.cn/apache/oltu/org.apache.oltu.oauth2/spring
项目中我将四种受权方式都作了实现(受权码模式、简化模式、密码模式及客户端模式),可是这里仅以受权码模式为例,服务端采用Jersey框架实现,而客户端采用spring mvc实现。sql
服务器端实现:为了方便开发,我将oltu的全部源码都拖进了项目中,而不是导入jar,由于不少地方可能在我所开发的项目中不适用,这样能够方便修改和跟踪代码。express
其实服务端开发很简单,主要集中在两个比较主要的文件中,一个是请求受权的AuthzEndpoint.java,一个是生成令牌的TokenEndpoint.java,如图所示。apache
至于资源控制器因为我开发的项目中,资源访问控制是采用过滤器的方式,所以没有用到oltu提供的java类,两个主要类文件的代码修改以下:json
AuthzEndpoint.java服务器
/**
* Copyright 2010 Newcastle University
*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
org.apache.oltu.oauth2.integration.endpoints;
import
java.net.URI;
import
java.net.URISyntaxException;
import
java.text.SimpleDateFormat;
import
java.util.HashMap;
import
java.util.Map;
import
java.util.Properties;
import
javax.servlet.http.HttpServletRequest;
import
javax.servlet.http.HttpServletResponse;
import
javax.ws.rs.GET;
import
javax.ws.rs.Path;
import
javax.ws.rs.core.Context;
import
javax.ws.rs.core.Response;
import
org.apache.ibatis.session.SqlSession;
import
org.apache.oltu.oauth2.as.issuer.MD5Generator;
import
org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl;
import
org.apache.oltu.oauth2.as.request.OAuthAuthzRequest;
import
org.apache.oltu.oauth2.as.response.OAuthASResponse;
import
org.apache.oltu.oauth2.common.OAuth;
import
org.apache.oltu.oauth2.common.error.OAuthError;
import
org.apache.oltu.oauth2.common.error.ServerErrorType;
import
org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import
org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import
org.apache.oltu.oauth2.common.message.OAuthResponse;
import
org.apache.oltu.oauth2.common.message.types.ResponseType;
import
org.apache.oltu.oauth2.integration.utils.Cache;
import
org.apache.oltu.oauth2.integration.utils.CacheManager;
import
com.cz.bean.App;
import
com.cz.bean.Authority;
import
com.cz.bean.RefreshToken;
import
com.cz.dao.AppMapper;
import
com.cz.dao.AuthorityMapper;
import
com.cz.dao.RefreshTokenMapper;
import
com.cz.util.DbUtil;
/**
*
* client request authorization
*
*/
@Path
(
"/authz"
)
public
class
AuthzEndpoint {
SqlSession sqlSession = DbUtil.getSessionFactory().openSession(
true
);
AppMapper appDao = sqlSession.getMapper(AppMapper.
class
);
AuthorityMapper authorityDao = sqlSession.getMapper(AuthorityMapper.
class
);
RefreshTokenMapper refreshTokenDao = sqlSession
.getMapper(RefreshTokenMapper.
class
);
//登陆页面
private
static
String loginPage;
//错误页面
private
static
String errorPage;
static
{
Properties p =
new
Properties();
try
{
p.load(AuthzEndpoint.
class
.getClassLoader().getResourceAsStream(
"config.properties"
));
loginPage = p.getProperty(
"loginPage"
);
errorPage = p.getProperty(
"errorPage"
);
}
catch
(Exception e) {
e.printStackTrace();
}
}
public
static
final
String INVALID_CLIENT_DESCRIPTION =
"Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)."
;
@GET
public
Response authorize(
@Context
HttpServletRequest request)
throws
URISyntaxException, OAuthSystemException {
OAuthAuthzRequest oauthRequest =
null
;
OAuthIssuerImpl oauthIssuerImpl =
new
OAuthIssuerImpl(
new
MD5Generator());
try
{
oauthRequest =
new
OAuthAuthzRequest(request);
/*
* 当前登陆的用户,模拟一个从session中获取的登陆用户
* 该方法未实现,待模块与养老平台整合时,应调用养老平台方法判断用户是否已登陆
* 并得到对应用户的userId
*/
String userId =
"1"
;
if
(
""
.equals(userId) || userId ==
null
) {
// 用户没有登陆就跳转到登陆页面
return
Response.temporaryRedirect(
new
URI(loginPage)).build();
}
App app =
null
;
if
(oauthRequest.getClientId()!=
null
&& !
""
.equals(oauthRequest.getClientId())){
app = appDao.selectByPrimaryKey(oauthRequest.getClientId());
}
else
{
return
Response.temporaryRedirect(
new
URI(errorPage+
"?error="
+ServerErrorType.CLIENT_ID_IS_NULL)).build();
}
// 根据response_type建立response
String responseType = oauthRequest
.getParam(OAuth.OAUTH_RESPONSE_TYPE);
OAuthASResponse.OAuthAuthorizationResponseBuilder builder = OAuthASResponse
.authorizationResponse(request,
HttpServletResponse.SC_FOUND);
// 检查传入的客户端id是否正确
if
(app ==
null
) {
return
Response.temporaryRedirect(
new
URI(errorPage+
"?error="
+ServerErrorType.UNKOWN_CLIENT_ID)).build();
}
String scope = oauthRequest.getParam(OAuth.OAUTH_SCOPE);
// 受权请求类型
if
(responseType.equals(ResponseType.CODE.toString())) {
String code = oauthIssuerImpl.authorizationCode();
builder.setCode(code);
CacheManager.putCache(userId+
"_code"
,
new
Cache(
"code"
, code,
216000000
,
false
));
CacheManager.putCache(userId+
"_scope"
,
new
Cache(
"scope"
, scope,
216000000
,
false
));
}
if
(responseType.equals(ResponseType.TOKEN.toString())) {
// 校验client_secret
if
(!app.getSecret_key().equals(oauthRequest.getClientSecret())) {
OAuthResponse response =
OAuthASResponse.errorResponse(HttpServletResponse.SC_OK)
.setError(OAuthError.TokenResponse.INVALID_CLIENT).setErrorDescription(INVALID_CLIENT_DESCRIPTION)
.buildJSONMessage();
return
Response.status(response.getResponseStatus()).entity(response.getBody()).build();
}
String accessToken = oauthIssuerImpl.accessToken();
builder.setAccessToken(accessToken);
builder.setExpiresIn(3600l);
//判断是否已经受权----待调整是放在authz部分仍是token部分
Map<String,Object> aQueryParam =
new
HashMap<>();
aQueryParam.put(
"appKey"
,oauthRequest.getClientId());
aQueryParam.put(
"userId"
,Integer.valueOf(userId));
if
(authorityDao.findUnique(aQueryParam)==
null
){
Authority authority =
new
Authority();
authority.setApp_key(oauthRequest.getClientId());
authority.setUser_id(Integer.valueOf(userId));
authorityDao.insert(authority);
}
// 存储token,已受权则更新令牌,未受权则新增令牌
Map<String,Object> rQueryParam =
new
HashMap<>();
rQueryParam.put(
"appKey"
, oauthRequest.getClientId());
rQueryParam.put(
"userId"
, Integer.valueOf(userId));
if
(refreshTokenDao.findUnique(rQueryParam) !=
null
) {
Map<String,Object> map =
new
HashMap<>();
map.put(
"accessToken"
, accessToken);
map.put(
"appKey"
, oauthRequest.getClientId());
map.put(
"userId"
, Integer.valueOf(userId));
map.put(
"createTime"
, getDate());
map.put(
"scope"
, scope);
map.put(
"authorizationTime"
, getDate());
refreshTokenDao.updateAccessToken(map);
}
else
{
RefreshToken rt =
new
RefreshToken();
rt.setApp_key(oauthRequest.getClientId());
rt.setUser_id(Integer.valueOf(userId));
rt.setAccess_token(accessToken);
rt.setCreate_time(getDate());
rt.setAuthorization_time(getDate());
rt.setExpire(
"3600"
);
rt.setScope(scope);
rt.setAuthorization_time(getDate());
refreshTokenDao.insert(rt);
}
}
// 客户端跳转URI
String redirectURI = oauthRequest
.getParam(OAuth.OAUTH_REDIRECT_URI);
final
OAuthResponse response = builder.location(redirectURI).setParam(
"scope"
, scope)
.buildQueryMessage();
String test = response.getLocationUri();
URI url =
new
URI(response.getLocationUri());
return
Response.status(response.getResponseStatus()).location(url)
.build();
}
catch
(OAuthProblemException e) {
return
Response.temporaryRedirect(
new
URI(errorPage+
"?error="
+ServerErrorType.BAD_RQUEST)).build();
}
}
private
String getDate() {
SimpleDateFormat sdf =
new
SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss"
);
return
sdf.format(System.currentTimeMillis());
}
}
|
上面的代码是受权码认证的第一步,当用户赞成受权以后向服务器请求受权码。你可使用一下腾讯的受权功能来加深一下体会,由于我所开发的模块也是参考腾讯的受权认证流程来实现的,客户端经过提交请求,访问相似http://192.168.19.75:10087/oauth/authz?client_id=s6BhdRkqt3&client_secret=12345&redirect_uri=http://localhost:8080/redirect.jsp&state=y&response_type=authorization_code的连接来访问上面的程序,参数的含义以下session
client_id :客户端idmvc
client_secret:客户端密钥
redirect_uri:回调地址,第三方应用定义的地址
State:状态,服务器将返回一个如出一辙的参数。
response_type:受权方式,这里必须是authorization_code,表示受权码 方式。
这个过程结束时,服务器会跳转至第三方应用定义的回调地址并附上受权码,而第三方经过这个回调地址得到受权码并进行相应的处理,而这个过程在oltu的实现中其实就是几行简单的代码:
// 建立response wrapper
OAuthAuthzResponse oar =
null
;
oar = OAuthAuthzResponse.oauthCodeAuthzResponse(request);
// 得到受权码
String code = oar.getCode();
|
上面的代码就是oltu客户端接收服务器发回的受权码的代码,其中request是一个HttpServletRequest对象,得到了受权码以后,按照下一步的流程,天然就是向受权服务器请求令牌并附上上一步得到的受权码。服务器得到受权码并进行相应处理的代码以下:
TokenEndpoint.java
/**
* Copyright 2010 Newcastle University
*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
org.apache.oltu.oauth2.integration.endpoints;
import
java.net.URI;
import
java.net.URISyntaxException;
import
java.text.SimpleDateFormat;
import
java.util.HashMap;
import
java.util.Map;
import
java.util.Properties;
import
javax.servlet.http.HttpServletRequest;
import
javax.servlet.http.HttpServletResponse;
import
javax.ws.rs.Consumes;
import
javax.ws.rs.POST;
import
javax.ws.rs.Path;
import
javax.ws.rs.Produces;
import
javax.ws.rs.core.Context;
import
javax.ws.rs.core.Response;
import
org.apache.ibatis.session.SqlSession;
import
org.apache.oltu.oauth2.as.issuer.MD5Generator;
import
org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
import
org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl;
import
org.apache.oltu.oauth2.as.request.OAuthTokenRequest;
import
org.apache.oltu.oauth2.as.response.OAuthASResponse;
import
org.apache.oltu.oauth2.common.OAuth;
import
org.apache.oltu.oauth2.common.error.OAuthError;
import
org.apache.oltu.oauth2.common.error.ServerErrorType;
import
org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import
org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import
org.apache.oltu.oauth2.common.message.OAuthResponse;
import
org.apache.oltu.oauth2.common.message.types.GrantType;
import
org.apache.oltu.oauth2.integration.utils.CacheManager;
import
com.cz.bean.App;
import
com.cz.bean.Authority;
import
com.cz.bean.RefreshToken;
import
com.cz.bean.User;
import
com.cz.dao.AppMapper;
import
com.cz.dao.AuthorityMapper;
import
com.cz.dao.RefreshTokenMapper;
import
com.cz.dao.UserMapper;
import
com.cz.util.DbUtil;
/**
*
* get access token
*
*/
@Path
(
"/token"
)
public
class
TokenEndpoint {
SqlSession sqlSession = DbUtil.getSessionFactory().openSession(
true
);
AppMapper appDao = sqlSession.getMapper(AppMapper.
class
);
RefreshTokenMapper refreshTokenDao = sqlSession
.getMapper(RefreshTokenMapper.
class
);
UserMapper dao = sqlSession.getMapper(UserMapper.
class
);
AuthorityMapper authorityDao = sqlSession.getMapper(AuthorityMapper.
class
);
// 登陆页面
private
static
String loginPage;
// 错误页面
private
static
String errorPage;
static
{
Properties p =
new
Properties();
try
{
p.load(AuthzEndpoint.
class
.getClassLoader().getResourceAsStream(
"config.properties"
));
loginPage = p.getProperty(
"loginPage"
);
errorPage = p.getProperty(
"errorPage"
);
}
catch
(Exception e) {
e.printStackTrace();
}
}
public
static
final
String INVALID_CLIENT_DESCRIPTION =
"Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)."
;
@SuppressWarnings
({
"unchecked"
,
"rawtypes"
})
@POST
@Consumes
(
"application/x-www-form-urlencoded"
)
@Produces
(
"application/json"
)
public
Response authorize(
@Context
HttpServletRequest request)
throws
OAuthSystemException, URISyntaxException {
OAuthTokenRequest oauthRequest =
null
;
String scope =
""
;
OAuthIssuer oauthIssuerImpl =
new
OAuthIssuerImpl(
new
MD5Generator());
try
{
oauthRequest =
new
OAuthTokenRequest(request);
/*
* 当前登陆的用户,模拟一个从session中获取的登陆用户
* 该方法未实现,待模块与养老平台整合时,应调用养老平台方法判断用户是否已登陆
*/
String userId =
"1"
;
if
(
""
.equals(userId) || userId ==
null
) {
// 用户没有登陆的话就跳转到登陆页面
return
Response.temporaryRedirect(
new
URI(loginPage)).build();
}
App app =
null
;
if
(oauthRequest.getClientId() !=
null
&& !
""
.equals(oauthRequest.getClientId())) {
app = appDao.selectByPrimaryKey(oauthRequest.getClientId());
}
else
{
return
Response.temporaryRedirect(
new
URI(errorPage +
"?error="
+ ServerErrorType.CLIENT_ID_IS_NULL)).build();
}
// 校验clientid
if
(app ==
null
|| !app.getApp_key().toString().equals(oauthRequest.getClientId())) {
if
(oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(GrantType.AUTHORIZATION_CODE.toString())){
return
Response.temporaryRedirect(
new
URI(errorPage +
"?error="
+ ServerErrorType.UNKOWN_CLIENT_ID)).build();
}
else
{
OAuthResponse response =
OAuthASResponse.errorResponse(HttpServletResponse.SC_OK)
.setError(OAuthError.TokenResponse.INVALID_CLIENT).setErrorDescription(INVALID_CLIENT_DESCRIPTION)
.buildJSONMessage();
return
Response.status(response.getResponseStatus()).entity(response.getBody()).build();
}
}
// 校验client_secret
if
(!app.getSecret_key().equals(oauthRequest.getClientSecret())) {
if
(oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(GrantType.AUTHORIZATION_CODE.toString())){
return
Response.temporaryRedirect(
new
URI(errorPage +
"?error="
+ ServerErrorType.UNKOWN_CLIENT_SECRET)).build();
}
else
{
OAuthResponse response =
OAuthASResponse.errorResponse(HttpServletResponse.SC_OK)
.setError(OAuthError.TokenResponse.INVALID_CLIENT).setErrorDescription(INVALID_CLIENT_DESCRIPTION)
.buildJSONMessage();
return
Response.status(response.getResponseStatus()).entity(response.getBody()).build();
}
}
// 校验不一样类型的受权方式
if
(oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(GrantType.AUTHORIZATION_CODE.toString())) {
String cacheCode =
null
;
if
(CacheManager.getCacheInfo(userId +
"_code"
).getValue() !=
null
) {
cacheCode = CacheManager.getCacheInfo(userId +
"_code"
)
.getValue().toString();
}
else
{
// 用户没有登陆的话就跳转到登陆页面
return
Response.temporaryRedirect(
new
URI(loginPage)).build();
}
if
(!cacheCode.equals(oauthRequest.getParam(OAuth.OAUTH_CODE))) {
return
Response.temporaryRedirect(
new
URI(errorPage+
"?error="
+ ServerErrorType.INVALID_AUTHORIZATION_CODE)).build();
}
if
(CacheManager.getCacheInfo(userId+
"_scope"
).getValue()!=
null
){
scope = CacheManager.getCacheInfo(userId+
"_scope"
).getValue().toString();
}
}
else
if
(oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(GrantType.PASSWORD.toString())) {
User user = dao.getById(userId);
if
(!user.getPassword().equals(oauthRequest.getPassword())|| !user.getName().equals(oauthRequest.getUsername())) {
OAuthResponse response = OAuthASResponse
.errorResponse(HttpServletResponse.SC_OK)
.setError(OAuthError.TokenResponse.INVALID_CLIENT)
.setErrorDescription(
"Invalid username or password."
)
.buildJSONMessage();
return
Response.status(response.getResponseStatus()).entity(response.getBody()).build();
}
}
else
if
(oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(
GrantType.CLIENT_CREDENTIALS.toString())) {
// 客户端id以及secret已验证,更多验证规则在这里添加,没有其余验证则程序直接发放令牌
// OAuthResponse response = OAuthASResponse
// .errorResponse(HttpServletResponse.SC_OK)
// .setError(OAuthError.TokenResponse.INVALID_GRANT)
// .setErrorDescription("invalid client")
// .buildJSONMessage();
// return Response.status(response.getResponseStatus()).entity(response.getBody()).build();
}
else
if
(oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(
GrantType.REFRESH_TOKEN.toString())) {
// 刷新令牌未实现
}
String accessToken = oauthIssuerImpl.accessToken();
String refreshToken = oauthIssuerImpl.refreshToken();
// 构建响应
OAuthResponse response = OAuthASResponse
.tokenResponse(HttpServletResponse.SC_OK)
.setAccessToken(accessToken).setRefreshToken(refreshToken)
.setExpiresIn(
"3600"
)
.buildJSONMessage();
// 判断是否已经受权----待调整是放在authz部分仍是token部分
Map aQueryParam =
new
HashMap();
aQueryParam.put(
"appKey"
, oauthRequest.getClientId());
aQueryParam.put(
"userId"
, Integer.valueOf(userId));
if
(authorityDao.findUnique(aQueryParam) ==
null
) {
Authority authority =
new
Authority();
authority.setApp_key(oauthRequest.getClientId());
authority.setUser_id(Integer.valueOf(userId));
authorityDao.insert(authority);
}
// String scope = "";
// if(CacheManager.getCacheInfo(userId+"_scope").getValue()!=null){
// scope = CacheManager.getCacheInfo(userId+"_scope").getValue().toString();
// }
// 存储token,已受权则更新令牌,未受权则新增令牌
Map rQueryParam =
new
HashMap();
rQueryParam.put(
"appKey"
, oauthRequest.getClientId());
rQueryParam.put(
"userId"
, Integer.valueOf(userId));
if
(refreshTokenDao.findUnique(rQueryParam) !=
null
) {
Map map =
new
HashMap();
map.put(
"accessToken"
, accessToken);
map.put(
"appKey"
, oauthRequest.getClientId());
map.put(
"userId"
, Integer.valueOf(userId));
map.put(
"createTime"
, getDate());
map.put(
"scope"
, scope);
map.put(
"authorizationTime"
, getDate());
refreshTokenDao.updateAccessToken(map);
}
else
{
RefreshToken rt =
new
RefreshToken();
rt.setApp_key(oauthRequest.getClientId());
rt.setUser_id(Integer.valueOf(userId));
rt.setAccess_token(accessToken);
rt.setRefresh_token(refreshToken);
rt.setCreate_time(getDate());
rt.setAuthorization_time(getDate());
rt.setExpire(
"3600"
);
rt.setScope(scope);
rt.setAuthorization_time(getDate());
refreshTokenDao.insert(rt);
}
return
Response.status(response.getResponseStatus())
.entity(response.getBody()).build();
}
catch
(OAuthProblemException e) {
System.out.println(e.getDescription());
return
Response.temporaryRedirect(
new
URI(errorPage +
"?error="
+ ServerErrorType.BAD_RQUEST)).build();
}
}
private
String getDate() {
SimpleDateFormat sdf =
new
SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss"
);
return
sdf.format(System.currentTimeMillis());
}
}
|
上面的代码,处理了客户端发来的申请令牌请求,并向客户端发放访问令牌,而oltu的客户端则经过以下代码来完成这个请求令牌和解析令牌的过程:
OAuthClient client =
new
OAuthClient(
new
URLConnectionClient());
OAuthAccessTokenResponse oauthResponse =
null
;
oauthResponse =
client.accessToken(request, OAuth.HttpMethod.POST);
String token = oauthResponse.getRefreshToken();
|
若是你是第一次开发,oauth2.0的认证过程可能会让你以为头疼,由于你首先须要对这个流程很熟悉,而且同时要看懂了oltu的代码才好理解这个开源的项目究竟是怎么实现这个过程的,所以这里我不过多的粘贴代码,由于这并无什么卵用,仍是运行项目和追踪代码比较容易理解它的原理,下面是我实现的项目代码,代码写得比较简陋,不过对于跟我同样的菜鸟,仍是能起到必定的帮助的~
服务端:https://git.oschina.net/honganlei/oauth-server.git
服务端受权登陆页面:https://git.oschina.net/honganlei/OauthClient.git
第三方接入受权模块的例子:https://git.oschina.net/honganlei/OauthApp.git