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