优化OAuth2.0

This commit is contained in:
zhou-hao
2017-10-28 01:55:58 +08:00
parent fb6fbb58ce
commit 89cd3aedb6
19 changed files with 241 additions and 57 deletions

View File

@@ -48,7 +48,22 @@
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-commons-utils</artifactId>
<version>3.0-SNAPSHOT</version>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-commons-controller</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,32 @@
package org.hswebframework.web.authorization.oauth2.server;
import org.hswebframework.web.authorization.oauth2.server.exception.GrantTokenException;
import org.hswebframework.web.controller.message.ResponseMessage;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Configuration
public class OAuth2ServerAutoConfiguration{
@Bean
public OAuth2ServerErrorControllerAdvice oAuth2ServerErrorControllerAdvice(){
return new OAuth2ServerErrorControllerAdvice();
}
/**
* @author zhouhao
*/
@RestControllerAdvice
public static class OAuth2ServerErrorControllerAdvice {
@ExceptionHandler(GrantTokenException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResponseMessage<String> error(GrantTokenException e) {
return ResponseMessage.<String>error(e.getErrorType().code(),e.getMessage())
.result(e.getErrorType().message());
}
}
}

View File

@@ -59,7 +59,7 @@ public class DefaultRefreshTokenGranter extends AbstractAuthorizationService imp
OAuth2AccessToken accessToken = accessTokenService.getTokenByRefreshToken(refreshToken);
if (accessToken == null) {
throw new GrantTokenException(ILLEGAL_REFRESH_TOKEN);
throw new GrantTokenException(EXPIRED_REFRESH_TOKEN);
}
if (System.currentTimeMillis() - accessToken.getCreateTime() > refreshTokenTimeOut) {
throw new GrantTokenException(EXPIRED_REFRESH_TOKEN);

View File

@@ -0,0 +1,3 @@
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.hswebframework.web.authorization.oauth2.server.OAuth2ServerAutoConfiguration

View File

@@ -60,5 +60,10 @@
<artifactId>hsweb-expands-request</artifactId>
<version>${hsweb.expands.version}</version>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-commons-controller</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -21,6 +21,8 @@ package org.hswebframework.web.authorization.oauth2.client.exception;
import org.hswebframework.web.authorization.oauth2.client.response.OAuth2Response;
import org.hswebframework.web.oauth2.core.ErrorType;
import java.io.PrintStream;
/**
* @author zhouhao
*/
@@ -35,6 +37,12 @@ public class OAuth2RequestException extends RuntimeException {
this.response = response;
}
public OAuth2RequestException(String message,ErrorType errorType, OAuth2Response response) {
super(message);
this.errorType = errorType;
this.response = response;
}
public ErrorType getErrorType() {
return errorType;
}
@@ -42,4 +50,5 @@ public class OAuth2RequestException extends RuntimeException {
public OAuth2Response getResponse() {
return response;
}
}

View File

@@ -29,6 +29,8 @@ import java.util.function.Consumer;
*/
public interface OAuth2Request {
OAuth2Request onRefreshTokenExpired(TokenExpiredCallBack refreshTokenExpiredCallBack);
OAuth2Request onTokenExpired(TokenExpiredCallBack callback);
/**

View File

@@ -20,8 +20,6 @@ package org.hswebframework.web.authorization.oauth2.client.request;
/**
* TODO 完成注释
*
* @author zhouhao
*/
public interface ReTry {

View File

@@ -19,16 +19,23 @@
package org.hswebframework.web.authorization.oauth2.client.simple.provider;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.builder.AuthenticationBuilderFactory;
import org.hswebframework.web.authorization.oauth2.client.exception.OAuth2RequestException;
import org.hswebframework.web.authorization.oauth2.client.request.definition.ResponseConvertForProviderDefinition;
import org.hswebframework.web.authorization.oauth2.client.response.OAuth2Response;
import org.hswebframework.web.controller.message.ResponseMessage;
import org.hswebframework.web.oauth2.core.ErrorType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author zhouhao
@@ -37,30 +44,97 @@ public class HswebResponseConvertSupport implements ResponseConvertForProviderDe
private AuthenticationBuilderFactory authenticationBuilderFactory;
private static int responseMessageFieldSize = 4;
Function<Object,Authentication> autzParser=obj-> convertAuthentication(JSON.toJSONString(obj));
public HswebResponseConvertSupport(AuthenticationBuilderFactory authenticationBuilderFactory) {
this.authenticationBuilderFactory = authenticationBuilderFactory;
}
public Object tryConvertToObject(String json, Class type) {
if (json.startsWith("{")) {
JSONObject message = JSON.parseObject(json, Feature.DisableFieldSmartMatch);
//判断是否响应的为ResponseMessage
if(message.size()<=responseMessageFieldSize
&&message.get("status")!=null&&message.get("timestamp")!=null){
Object data = message.get("result");
if(data==null){
return null;
}
//返回的是对象
if (data instanceof JSONObject) {
if (type == Authentication.class) {
return autzParser.apply(data);
}
return ((JSONObject) data).toJavaObject(type);
}
//返回的是集合
if (data instanceof JSONArray) {
if (type == Authentication.class) {
return ((JSONArray) data).stream().map(autzParser).collect(Collectors.toList());
}
return ((JSONArray) data).toJavaList(type);
}
return data;
}
return message.toJavaObject(type);
} else if (json.startsWith("[")) {
if (type == Authentication.class) {
return (JSON.parseArray(json)).stream().map(autzParser).collect(Collectors.toList());
}
return JSON.parseArray(json, type);
}
return null;
}
protected <T> T convertAuthentication(String json) {
if (authenticationBuilderFactory != null) {
return (T) authenticationBuilderFactory.create().json(json).build();
} else {
throw new UnsupportedOperationException("authenticationBuilderFactory not ready");
}
}
@Override
public <T> T convert(OAuth2Response response, Class<T> type) {
String json = response.asString();
if (response.status() != 200) {
throw new OAuth2RequestException(ErrorType.OTHER, response);
Object data = tryConvertToObject(json, type);
if(null==data)return null;
if (type.isInstance(data)) {
//success
return ((T) data);
}
if (type == Authentication.class) {
if (authenticationBuilderFactory != null) {
return (T) authenticationBuilderFactory.create().json(json).build();
} else {
throw new UnsupportedOperationException("authenticationBuilderFactory not ready");
}
if (data instanceof ResponseMessage) {
//maybe error
throw new OAuth2RequestException(((ResponseMessage) data).getMessage(),ErrorType.SERVICE_ERROR, response);
}
return JSON.parseObject(json, type);
throw new OAuth2RequestException(ErrorType.PARSE_RESPONSE_ERROR, response);
}
@Override
@SuppressWarnings("all")
public <T> List<T> convertList(OAuth2Response response, Class<T> type) {
String json = response.asString();
return JSON.parseArray(json, type);
Object data = tryConvertToObject(json, type);
if(null==data)return null;
if (data instanceof List) {
//success
return ((List) data);
}
if (data instanceof ResponseMessage) {
//maybe error
throw new OAuth2RequestException(((ResponseMessage) data).getMessage(),ErrorType.SERVICE_ERROR, response);
}
throw new OAuth2RequestException(ErrorType.PARSE_RESPONSE_ERROR, response);
}
@Override

View File

@@ -37,6 +37,9 @@ public class HswebResponseJudgeSupport implements ResponseJudgeForProviderDefini
@Override
public ErrorType judge(OAuth2Response response) {
if(response.status()!=500){
return null;
}
String result = response.asString();
if (result == null) {
return ErrorType.OTHER;

View File

@@ -25,6 +25,7 @@ import org.hswebframework.web.authorization.oauth2.client.request.ResponseConver
import org.hswebframework.web.authorization.oauth2.client.request.ResponseJudge;
import org.hswebframework.web.authorization.oauth2.client.request.TokenExpiredCallBack;
import org.hswebframework.web.authorization.oauth2.client.response.OAuth2Response;
import org.hswebframework.web.oauth2.core.ErrorType;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -42,6 +43,8 @@ public class SimpleOAuth2Request implements OAuth2Request {
private TokenExpiredCallBack expiredCallBack;
private TokenExpiredCallBack refreshTokenExpiredCallBack;
public SimpleOAuth2Request(HttpRequest request) {
this.request = request;
}
@@ -54,6 +57,12 @@ public class SimpleOAuth2Request implements OAuth2Request {
this.responseJudge = responseJudge;
}
@Override
public OAuth2Request onRefreshTokenExpired(TokenExpiredCallBack refreshTokenExpiredCallBack){
this.refreshTokenExpiredCallBack=refreshTokenExpiredCallBack;
return this;
}
@Override
public OAuth2Request onTokenExpired(TokenExpiredCallBack callback) {
this.expiredCallBack = callback;
@@ -104,16 +113,29 @@ public class SimpleOAuth2Request implements OAuth2Request {
private volatile SimpleOAuth2Response auth2Response;
protected SimpleOAuth2Response createNativeResponse(Supplier<Response> responseSupplier) {
return auth2Response = new SimpleOAuth2Response(responseSupplier.get(), convertHandler, responseJudge);
SimpleOAuth2Response response= new SimpleOAuth2Response(responseSupplier.get(), convertHandler, responseJudge);
return auth2Response =response;
}
protected OAuth2Response createResponse(Supplier<Response> responseSupplier) {
createNativeResponse(responseSupplier);
if (null != expiredCallBack) {
//判定token是否过期,过期后先执行回调进行操作如更新token,并尝试重新请求
auth2Response.judgeExpired(() -> {
auth2Response.judgeError(ErrorType.EXPIRED_TOKEN,() -> {
//调用回调,并指定重试的操作(重新请求)
expiredCallBack.call(() -> createNativeResponse(responseSupplier));
//返回重试后的response
return auth2Response;
});
}
if (null != refreshTokenExpiredCallBack) {
//判定refresh_token是否过期,过期后先执行回调进行操作如更新token,并尝试重新请求
auth2Response.judgeError(ErrorType.EXPIRED_REFRESH_TOKEN,() -> {
//调用回调,并指定重试的操作(重新请求)
refreshTokenExpiredCallBack.call(() -> createNativeResponse(responseSupplier));
//返回重试后的response
return auth2Response;
});

View File

@@ -31,6 +31,8 @@ import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import static org.hswebframework.web.oauth2.core.ErrorType.ILLEGAL_REFRESH_TOKEN;
/**
* @author zhouhao
*/
@@ -48,8 +50,10 @@ public class SimpleOAuth2Response implements OAuth2Response {
private OAuth2Response proxy = this;
public void judgeExpired(Supplier<OAuth2Response> expiredCallBack) {
if (errorType == ErrorType.EXPIRED_TOKEN) {
public void judgeError(ErrorType ifError,Supplier<OAuth2Response> expiredCallBack) {
if (errorType == ifError) {
//尝试执行认证过时回调进行重试,并返回重试的结果
OAuth2Response retryRes = expiredCallBack.get();
if (retryRes == null) {
@@ -57,9 +61,10 @@ public class SimpleOAuth2Response implements OAuth2Response {
}
proxy = retryRes;
proxy.onError((retryResponse, type) -> {
if (type == ErrorType.EXPIRED_TOKEN) {
//重试后依然是认证过时,可能是错误类型判断错误或者服务端的问题?
logger.warn("still error [expired_token], maybe judge error or auth server error ");
if (type == ifError) {
//重试后依然是相同的错误,可能是错误类型判断错误或者服务端的问题?
logger.warn("still error [{}], maybe judge error or auth server error ",ifError);
} else {
errorType = type;
}
@@ -119,4 +124,7 @@ public class SimpleOAuth2Response implements OAuth2Response {
return proxy;
}
public ErrorType getErrorType() {
return errorType;
}
}

View File

@@ -23,6 +23,7 @@ import org.hswebframework.web.authorization.oauth2.client.*;
import org.hswebframework.web.authorization.oauth2.client.request.OAuth2Request;
import org.hswebframework.web.authorization.oauth2.client.request.OAuth2Session;
import org.hswebframework.web.authorization.oauth2.client.response.OAuth2Response;
import org.hswebframework.web.oauth2.core.ErrorType;
import org.springframework.util.Assert;
import java.util.concurrent.locks.ReadWriteLock;
@@ -122,6 +123,12 @@ public class DefaultOAuth2Session implements OAuth2Session {
applyTokenParam(request); //重设请求参数
retry.doReTry(); //执行重试
});
request.onRefreshTokenExpired(reTry -> {
//重新请求token
setAccessTokenInfo(requestAccessToken());
applyTokenParam(request);
reTry.doReTry();
});
applyTokenParam(request);
return request;
}
@@ -139,6 +146,7 @@ public class DefaultOAuth2Session implements OAuth2Session {
.post().onError(OAuth2Response.throwOnError)
.as(AccessTokenInfo.class);
accessTokenInfo.setCreateTime(System.currentTimeMillis());
accessTokenInfo.setUpdateTime(System.currentTimeMillis());
return accessTokenInfo;
}
@@ -147,13 +155,31 @@ public class DefaultOAuth2Session implements OAuth2Session {
return;
}
OAuth2Request request = createRequest(getRealUrl(serverConfig.getAccessTokenUrl()));
request.onRefreshTokenExpired(reTry -> {
//重新请求token
setAccessTokenInfo(requestAccessToken());
applyTokenParam(request);
reTry.doReTry();
});
applyBasicAuthParam(request);
boolean[] skip = new boolean[1];
AccessTokenInfo tokenInfo = request
.param(OAuth2Constants.scope, scope)
.param(OAuth2Constants.grant_type, GrantType.refresh_token)
.param(GrantType.refresh_token, accessTokenInfo.getRefreshToken())
.post().onError(OAuth2Response.throwOnError)
.post().onError((oAuth2Response, type) -> {
if(type== ErrorType.EXPIRED_REFRESH_TOKEN){
setAccessTokenInfo(requestAccessToken());
skip[0]=true;
return;
}
OAuth2Response.throwOnError.accept(oAuth2Response,type);
})
.as(AccessTokenInfo.class);
if(skip[0]){
return;
}
tokenInfo.setCreateTime(accessTokenInfo.getCreateTime());
tokenInfo.setUpdateTime(System.currentTimeMillis());
setAccessTokenInfo(tokenInfo);

View File

@@ -58,7 +58,13 @@ public enum ErrorType {
USER_NOT_EXIST(4041),//客户端不存在
ACCESS_DENIED(503), //访问被拒绝
OTHER(5001); //其他错误 ;
OTHER(5001), //其他错误 ;
PARSE_RESPONSE_ERROR(5002),//解析返回结果错误
SERVICE_ERROR(5003); //服务器返回错误信息
private final String message;
private final int code;

View File

@@ -105,5 +105,10 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-concurrent-cache</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -103,5 +103,6 @@
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -24,14 +24,15 @@ import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.AuthenticationHolder;
import org.hswebframework.web.authorization.exception.UnAuthorizedException;
import org.hswebframework.web.authorization.oauth2.server.OAuth2AccessToken;
import org.hswebframework.web.authorization.oauth2.server.exception.GrantTokenException;
import org.hswebframework.web.authorization.oauth2.server.token.AccessTokenService;
import org.hswebframework.web.controller.message.ResponseMessage;
import org.hswebframework.web.oauth2.core.ErrorType;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* TODO 完成注释
*
* @author zhouhao
*/
@RestController
@@ -45,24 +46,24 @@ public class OAuth2UserInfoController {
@GetMapping
@ApiOperation("根据accessToken获取用户信息")
public Authentication getLoginUser(@RequestParam("access_token") String access_token) {
public ResponseMessage<Authentication> getLoginUser(@RequestParam("access_token") String access_token) {
OAuth2AccessToken auth2AccessEntity = accessTokenService.getTokenByAccessToken(access_token);
if (null == auth2AccessEntity) {
throw new UnAuthorizedException();
throw new GrantTokenException(ErrorType.EXPIRED_TOKEN);
}
return AuthenticationHolder.get(auth2AccessEntity.getOwnerId());
return ResponseMessage.ok(AuthenticationHolder.get(auth2AccessEntity.getOwnerId()));
}
@GetMapping("/{userId}")
@ApiOperation("根据accessToken获取用户信息")
public Authentication getUserById(
public ResponseMessage<Authentication> getUserById(
@PathVariable("userId") String userId,
@RequestParam("access_token") String access_token) {
OAuth2AccessToken auth2AccessEntity = accessTokenService.getTokenByAccessToken(access_token);
if (null == auth2AccessEntity) {
throw new UnAuthorizedException();
throw new GrantTokenException(ErrorType.EXPIRED_TOKEN);
}
return AuthenticationHolder.get(userId);
return ResponseMessage.ok(AuthenticationHolder.get(userId));
}
}

View File

@@ -64,11 +64,6 @@ public class OAuth2GranterAutoConfiguration {
@Autowired(required = false)
private TokenGenerator tokenGenerator;
@Bean
public OAuth2ServerErrorControllerAdvice oAuth2ServerErrorControllerAdvice() {
return new OAuth2ServerErrorControllerAdvice();
}
@ConditionalOnMissingBean(AuthorizationCodeService.class)
@Bean
public SimpleAuthorizationCodeService simpleAuthorizationCodeService(AuthorizationCodeDao authorizationCodeDao,

View File

@@ -1,21 +0,0 @@
package org.hswebframework.web.oauth2;
import org.hswebframework.web.authorization.oauth2.server.exception.GrantTokenException;
import org.hswebframework.web.controller.message.ResponseMessage;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* @author zhouhao
*/
@RestControllerAdvice
public class OAuth2ServerErrorControllerAdvice {
@ExceptionHandler(GrantTokenException.class)
@ResponseStatus(HttpStatus.OK)
public ResponseMessage<String> error(GrantTokenException e) {
return ResponseMessage.error(e.getErrorType().code(), e.getErrorType().message());
}
}