前面我们使用 Spring Security+OAuth2做认证授权时,默认返回都是提供好的默认返回格式,返回结果不是很友好,大体如下:
{
"error": "invalid_client",
"error_description": "Bad client credentials"
}
针对这些异常,我们自定义返回格式内容,做异常统一返回格式处理。
首先结果集基类格式如下:
public class BaseResult implements Serializable {
private static final long serialVersionUID = 1L;
protected boolean success;
protected String message;
protected String detailMessage;
protected Object data;
...getter/setter
}
一、资源服务异常处理
资源服异常场景,一般就是分为 token异常(未携带token,token解析失败,token过期或者无效token)和权限不足异常。
OAuth2AuthenticationProcessingFilter 是 OAuth2受保护资源的身份验证过滤器。在它的 doFilter方法中我们大致了解其抛出的异常类型。所以这个处理起来还是比较简单的。
1、token异常
创建 MyExtendAuthenticationEntryPointHandler类实现 OAuth2AuthenticationEntryPoint接口。
@Component
public class MyExtendAuthenticationEntryPointHandler extends OAuth2AuthenticationEntryPoint {
private static final Logger log = LoggerFactory.getLogger(MyExtendAuthenticationEntryPointHandler.class);
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
Throwable cause = authException.getCause();
BaseResult baseResult = new BaseResult();
baseResult.setSuccess(false);
baseResult.setDetailMessage(authException.getMessage());
baseResult.setMessage(authException.getMessage());
if (cause instanceof OAuth2AccessDeniedException) {
baseResult.setMessage("资源ID不在resource_ids范围内");
} else if (cause instanceof InvalidTokenException) {
baseResult.setMessage("Token解析失败");
}else if (authException instanceof InsufficientAuthenticationException) {
baseResult.setMessage("未携带token");
}else{
baseResult.setMessage("未知异常信息");
}
response.setStatus(HttpStatus.OK.value());
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type", "application/json;charset=UTF-8");
PrintWriter printWriter = response.getWriter();
printWriter.append(new ObjectMapper().writeValueAsString(baseResult));
}
}
2、权限不足异常
创建 MyExtendAccessDeniedHandler类实现 AccessDeniedHandler接口。
@Component
public class MyExtendAccessDeniedHandler implements AccessDeniedHandler {
private static final Logger log = LoggerFactory.getLogger(MyExtendAccessDeniedHandler.class);
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
BaseResult baseResult = new BaseResult();
baseResult.setSuccess(false);
baseResult.setMessage("认证过的用户访问无权限资源时的异常");
baseResult.setDetailMessage(accessDeniedException.getMessage());
response.setStatus(HttpStatus.OK.value());
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type", "application/json;charset=UTF-8");
response.setStatus(HttpStatus.FORBIDDEN.value());
response.getWriter().write(new ObjectMapper().writeValueAsString(baseResult));
}
}
3、在资源配置类中配置处理器
在资源配置类中配置自定义的处理器。
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("order_source_api")
.tokenStore(jdbcTokenStore())
.authenticationEntryPoint(myExtendAuthenticationEntryPointHandler)
.accessDeniedHandler(myExtendAccessDeniedHandler)
;
}
重启资源服务,自定义响应结果ok。
二、认证服务异常处理
在 AuthorizationServerEndpointsConfigurer端点配置类有一个 WebResponseExceptionTranslator异常翻译器 。
DefaultWebResponseExceptionTranslator类是 WebResponseExceptionTranslator的唯一默认实现类。
所以,创建 MyExtendAuth2ResponseExceptionTranslator类实现 WebResponseExceptionTranslator接口。 然后重写 translate方法,translate方法就是根据不同的异常栈返回不同的异常对象。可以参考 DefaultWebResponseExceptionTranslator类。
1、创建 MyExtendAuth2ResponseExceptionTranslator类
@Component
public class MyExtendOAuth2ResponseExceptionTranslator implements WebResponseExceptionTranslator<OAuth2Exception> {
private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
@Override
public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(e);
Exception ase = (OAuth2Exception) throwableAnalyzer.getFirstThrowableOfType(OAuth2Exception.class, causeChain);
if (ase != null) {
return handleOAuth2Exception((OAuth2Exception) ase);
}
return handleOAuth2Exception(new ServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), e));
}
private ResponseEntity<OAuth2Exception> handleOAuth2Exception(OAuth2Exception e) throws IOException {
int status = e.getHttpErrorCode();
HttpHeaders headers = new HttpHeaders();
headers.set("Cache-Control", "no-store");
headers.set("Pragma", "no-cache");
if (status == HttpStatus.UNAUTHORIZED.value() || (e instanceof InsufficientScopeException)) {
headers.set("WWW-Authenticate", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, e.getSummary()));
}
e = new MyExtendOAuth2Exception(e.getMessage(), e.getOAuth2ErrorCode(), e);
ResponseEntity<OAuth2Exception> response = new ResponseEntity<OAuth2Exception>(e, headers, HttpStatus.valueOf(status));
return response;
}
}
translate方法这里简单点,除了 OAuth2Exception异常就是自定义的 ServerErrorException异常。 handleOAuth2Exception方法是,将 OAuth2Exception异常包装成 自定义的 MyExtendOAuth2Exception异常。
其实我们可以根据不同的异常栈返回不同的异常对象,做不同的异常信息提示,我这里就全部放到 MyExtendOAuth2Exception类中处理,主要把 OAuth2Exception异常信息添加对应的中文信息返回。
1.1 MyExtendOAuth2Exception类
@JsonSerialize(using = MyExtendOAuth2ExceptionSerializer.class)
public class MyExtendOAuth2Exception extends OAuth2Exception {
private static final Logger log = LoggerFactory.getLogger(MyExtendOAuth2Exception.class);
public static final String ERROR = "error";
public static final String DESCRIPTION = "error_description";
public static final String URI = "error_uri";
public static final String INVALID_REQUEST = "invalid_request";
public static final String INVALID_CLIENT = "invalid_client";
public static final String INVALID_GRANT = "invalid_grant";
public static final String UNAUTHORIZED_CLIENT = "unauthorized_client";
public static final String UNSUPPORTED_GRANT_TYPE = "unsupported_grant_type";
public static final String INVALID_SCOPE = "invalid_scope";
public static final String INSUFFICIENT_SCOPE = "insufficient_scope";
public static final String INVALID_TOKEN = "invalid_token";
public static final String REDIRECT_URI_MISMATCH ="redirect_uri_mismatch";
public static final String UNSUPPORTED_RESPONSE_TYPE ="unsupported_response_type";
public static final String ACCESS_DENIED = "access_denied";
public static final String METHOD_NOT_ALLOWED = "method_not_allowed";
public static final String SERVER_ERROR = "server_error";
public static final String UNAUTHORIZED = "unauthorized";
private static ConcurrentHashMap<String, String> oAuth2ErrorMap = new ConcurrentHashMap<>();
static {
oAuth2ErrorMap.put(INVALID_CLIENT,"无效的客户端");
oAuth2ErrorMap.put(INVALID_GRANT,"无效的授权模式");
oAuth2ErrorMap.put(INVALID_SCOPE,"权限不足");
oAuth2ErrorMap.put(UNSUPPORTED_GRANT_TYPE,"不支持的授权模式类型");
oAuth2ErrorMap.put(ACCESS_DENIED,"拒绝访问");
oAuth2ErrorMap.put(METHOD_NOT_ALLOWED,"方法不允许访问");
oAuth2ErrorMap.put(SERVER_ERROR,"服务器内部异常");
oAuth2ErrorMap.put(UNAUTHORIZED,"未授权");
}
private String myExtendMessage;
public MyExtendOAuth2Exception(String msg, Throwable t) {
super(msg, t);
}
public MyExtendOAuth2Exception(String msg, String oAuth2ErrorCode, Throwable t) {
super(msg, t);
log.info("自定义扩展OAuth2异常处理 MyExtendOAuth2Exception.class -> msg={},oAuth2ErrorCode={}", msg, oAuth2ErrorCode);
String oAuth2ErrorMessage = oAuth2ErrorMap.get(oAuth2ErrorCode);
this.myExtendMessage = oAuth2ErrorMessage != null ? oAuth2ErrorMessage : "未知异常:" + oAuth2ErrorCode;
}
public String getMyExtendMessage() {
return myExtendMessage;
}
public void setMyExtendMessage(String myExtendMessage) {
this.myExtendMessage = myExtendMessage;
}
}
1.2 ServerErrorException类
public class ServerErrorException extends MyExtendOAuth2Exception {
public ServerErrorException(String msg, Throwable t) {
super(msg, t);
}
@Override
public String getOAuth2ErrorCode() {
return "server_error";
}
@Override
public int getHttpErrorCode() {
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
}
有了这些还不够,还需要异常序列化器,源码是最好的导师,可参考 OAuth2Exception类 。
2、创建 MyExtendOAuth2ExceptionSerializer类
创建 MyExtendOAuth2ExceptionSerializer类继承 StdSerializer类。
@Component
public class MyExtendOAuth2ExceptionSerializer extends StdSerializer<MyExtendOAuth2Exception> {
private static final Logger log = LoggerFactory.getLogger(MyExtendOAuth2ExceptionSerializer.class);
public MyExtendOAuth2ExceptionSerializer() {
super(MyExtendOAuth2Exception.class);
}
@Override
public void serialize(MyExtendOAuth2Exception e, JsonGenerator jGen, SerializerProvider provider) throws IOException {
log.info("MyCustomOAuth2Exception返回格式序列化处理,MyExtendOAuth2ExceptionSerializer.class -> e={}", e);
BaseResult baseResult = new BaseResult();
baseResult.setSuccess(false);
baseResult.setMessage(e.getMyExtendMessage());
baseResult.setDetailMessage(e.getMessage());
jGen.writeObject(baseResult);
}
}
3、在OAuth2配置类中配置异常翻译器
在 OAuth2配置类中配置自定义的异常翻译器。
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.userDetailsService(userService)
.approvalStore(approvalStore())
.authenticationManager(authenticationManager)
.authorizationCodeServices(authorizationCodeServices())
.tokenStore(tokenStore())
.exceptionTranslator(new MyExtendOAuth2ResponseExceptionTranslator())
;
}
重启服务,访问,我发现部分异常走了我们自定义的异常解析器,然后调用自定义的异常序列化器。 部分异常走的还是 DefaultWebResponseExceptionTranslator吗默认的异常解析器,然后调用OAuth2ExceptionJackson2Serializer异常序列化器。
对此,认证服务这边还没有做到完全的自定义统一异常处理,后面再研究研究。
– 求知若饥,虚心若愚。
|