赞
踩
前言
本篇文章的代码较多,但是核心的点是:exceptionTranslator、authenticationEntryPoint
只要百度搜索关键字基本上都会关于这两个的介绍
exceptionTranslator
/**
@description: oauth2 认证服务异常处理,重写oauth2的默认实现
@Author C_Y_J
@create 2022/1/12 9:48
**/
public class CustomWebResponseExceptionTranslator implements WebResponseExceptionTranslator {
private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
@Override
public ResponseEntity translate(Exception e) {
// Try to extract a SpringSecurityException from the stacktrace
// 尝试从堆栈跟踪中提取 SpringSecurityException
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(e);
// 未经授权的异常
Exception ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (ase != null) {
return handleOAuth2Exception(new UnauthorizedException(ase.getMessage(), ase));
}
// 访问被拒绝异常
ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
if (ase != null) {
return handleOAuth2Exception(new UnauthorizedException.ForbiddenException(ase.getMessage(), ase));
}
// InvalidGrantException grant_type不支持
ase = (InvalidGrantException) throwableAnalyzer.getFirstThrowableOfType(InvalidGrantException.class, causeChain);
if (ase != null) {
String msg = SecurityMessageSourceUtil.getAccessor().getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", ase.getMessage(), Locale.CHINA);
return handleOAuth2Exception(new UnauthorizedException.InvalidException(msg, ase));
}
// HttpRequestMethodNotSupportedException
ase = (HttpRequestMethodNotSupportedException) throwableAnalyzer.getFirstThrowableOfType(HttpRequestMethodNotSupportedException.class, causeChain);
if (ase != null) {
return handleOAuth2Exception(new UnauthorizedException.MethodNotAllowedException(ase.getMessage(), ase));
}
// 处理不合法的令牌错误 427 返回
ase = (InvalidTokenException) throwableAnalyzer.getFirstThrowableOfType(InvalidTokenException.class, causeChain);
if (ase != null) {
return handleOAuth2Exception(new UnauthorizedException.TokenInvalidException(ase.getMessage(), ase));
}
ase = (OAuth2Exception) throwableAnalyzer.getFirstThrowableOfType(OAuth2Exception.class, causeChain);
if (ase != null) {
return handleOAuth2Exception((OAuth2Exception) ase);
}
// 这个最后一个异常,默认为服务器挂了
String reasonPhrase = HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
return handleOAuth2Exception(new UnauthorizedException.ServerErrorException(reasonPhrase, e));
}
private ResponseEntity handleOAuth2Exception(OAuth2Exception e) {
int status = e.getHttpErrorCode();
// 客户端异常直接返回客户端,不然无法解析
if (e instanceof ClientAuthenticationException) {
return new ResponseEntity<>(e, HttpStatus.valueOf(status));
}
CustomOAuth2Exception exception = new CustomOAuth2Exception(e.getMessage(), e.getOAuth2ErrorCode());
return new ResponseEntity<>(exception, HttpStatus.valueOf(status));
}
}
/**
@description: 序列化实现
@Author C_Y_J
@create 2022/1/12 10:42
**/
public class CustomOAuth2ExceptionSerializer extends StdSerializer {
public CustomOAuth2ExceptionSerializer() {
super(CustomOAuth2Exception.class);
}
@Override
@SneakyThrows
public void serialize(CustomOAuth2Exception value, JsonGenerator gen, SerializerProvider provider) {
gen.writeStartObject();
gen.writeObjectField(“code”, 1);
gen.writeStringField(“msg”, value.getMessage());
gen.writeStringField(“data”, value.getErrorCode());
gen.writeEndObject();
}
}
/**
@description: 添加自定义OAuth2异常类,并指定json序列化方式
@Author C_Y_J
@create 2022/1/12 10:38
**/
@JsonSerialize(using = CustomOAuth2ExceptionSerializer.class)
public class CustomOAuth2Exception extends OAuth2Exception {
@Getter
private String errorCode;
public CustomOAuth2Exception(String msg) {
super(msg);
}
public CustomOAuth2Exception(String msg, Throwable t) {
super(msg, t);
}
public CustomOAuth2Exception(String msg, String errorCode) {
super(msg);
this.errorCode = errorCode;
}
}
/**
@description: 未经授权的异常
@Author C_Y_J
@create 2022/1/12 9:52
**/
public class UnauthorizedException extends CustomOAuth2Exception {
public UnauthorizedException(String msg, Throwable t) {
super(msg);
}
@Override
public String getOAuth2ErrorCode() {
return “unauthorized”;
}
@Override
public int getHttpErrorCode() {
return HttpStatus.UNAUTHORIZED.value();
}
/**
@description: 认为服务挂了
@Author C_Y_J
@create 2022/1/12 9:52
**/
public static class ServerErrorException extends CustomOAuth2Exception {
public ServerErrorException(String msg, Throwable t) {
super(msg);
}
@Override
public String getOAuth2ErrorCode() {
return “server_error”;
}
@Override
public int getHttpErrorCode() {
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
}
/**
@description: 禁止访问
@Author C_Y_J
@create 2022/1/12 9:52
**/
public static class ForbiddenException extends CustomOAuth2Exception {
public ForbiddenException(String msg, Throwable t) {
super(msg, t);
}
@Override
public String getOAuth2ErrorCode() {
return “access_denied”;
}
@Override
public int getHttpErrorCode() {
return HttpStatus.FORBIDDEN.value();
}
}
/**
@description: grant_type不支持
@Author C_Y_J
@create 2022/1/12 9:52
**/
public static class InvalidException extends CustomOAuth2Exception {
public InvalidException(String msg, Throwable t) {
super(msg);
}
@Override
public String getOAuth2ErrorCode() {
return “invalid_exception”;
}
@Override
public int getHttpErrorCode() {
return 426;
}
}
/**
@description:
@Author C_Y_J
@create 2022/1/12 9:52
**/
public static class MethodNotAllowedException extends CustomOAuth2Exception {
public MethodNotAllowedException(String msg, Throwable t) {
super(msg);
}
@Override
public String getOAuth2ErrorCode() {
return “method_not_allowed”;
}
@Override
public int getHttpErrorCode() {
return HttpStatus.METHOD_NOT_ALLOWED.value();
}
}
/**
@description:
@Author C_Y_J
@create 2022/1/12 9:52
**/
public static class TokenInvalidException extends CustomOAuth2Exception {
public TokenInvalidException(String msg, Throwable t) {
super(msg);
}
@Override
public String getOAuth2ErrorCode() {
return “invalid_token”;
}
@Override
public int getHttpErrorCode() {
return HttpStatus.FAILED_DEPENDENCY.value();
}
}
}
授权服务器中的错误处理使用标准的Spring MVC功能,即@ExceptionHandler端点本身中的方法。但是其原生的异常信息可能与我们实际使用的异常处理不一致,需要进行转义。可以自定义WebResponseExceptionTranslator,想授权端点提供异常处理,这是更改响应异常处理的最佳方法。这里重写了 WebResponseExceptionTranslator 的默认实现,可以去对比源码的实现。为了简洁我把很多类写在一起,实际在使用的过程中也可以拆开这些类。
/**
@description:
@Author C_Y_J
@create 2022/1/12 9:40
**/
public class SecurityMessageSourceUtil extends ReloadableResourceBundleMessageSource {
public SecurityMessageSourceUtil() {
setBasename(“classpath:messages/messages”);
setDefaultLocale(Locale.CHINA);
}
public static MessageSourceAccessor getAccessor() {
return new MessageSourceAccessor(new SecurityMessageSourceUtil());
}
}
AuthorizationServerConfiguration
private final CustomWebResponseExceptionTranslator webResponseExceptionTranslator;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
// 配置端点
endpoints.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
// 授权服务器端点的自定义异常处理
endpoints.exceptionTranslator(webResponseExceptionTranslator);
}
authenticationEntryPoint
/**
@description: 针对资源服务器的异常处理 {@link OAuth2AuthenticationProcessingFilter}不同细化异常处理
@Author C_Y_J
@create 2022/1/12 9:20
**/
@AllArgsConstructor
public class CustomOAuth2AuthenticationEntryPoint implements AuthenticationEntryPoint {
private final ObjectMapper objectMapper;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
// 用自己公司的封装工具类,仅供参看
R<String> result = new R<>();
result.setMsg(authException.getMessage());
result.setData(authException.getMessage());
result.setCode(1);
if (authException instanceof CredentialsExpiredException || authException instanceof InsufficientAuthenticationException) {
String msg = SecurityMessageSourceUtil.getAccessor().getMessage(
"AbstractUserDetailsAuthenticationProvider.credentialsExpired", authException.getMessage());
result.setMsg(msg);
}
if (authException instanceof UsernameNotFoundException) {
String msg = SecurityMessageSourceUtil.getAccessor().getMessage(
"AbstractUserDetailsAuthenticationProvider.noopBindAccount", authException.getMessage());
result.setMsg(msg);
}
if (authException instanceof BadCredentialsException) {
String msg = SecurityMessageSourceUtil.getAccessor().getMessage(
"AbstractUserDetailsAuthenticationProvider.badClientCredentials", authException.getMessage());
result.setMsg(msg);
}
if (authException instanceof InsufficientAuthenticationException) {
String msg = SecurityMessageSourceUtil.getAccessor()
.getMessage("AbstractAccessDecisionManager.expireToken", authException.getMessage());
// 默认是401,因为前后端约定令牌过期的状态码为424
response.setStatus(HttpStatus.FAILED_DEPENDENCY.value());
result.setMsg(msg);
}
PrintWriter printWriter = response.getWriter();
printWriter.append(objectMapper.writeValueAsString(result));
}
}
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class R implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 返回标记:成功标记=0,失败标记=1
*/
@Getter
@Setter
private int code;
/**
* 返回信息
*/
@Getter
@Setter
private String msg;
/**
* 数据
*/
@Getter
@Setter
private T data;
public static <T> R<T> ok() {
return restResult(null, 0, null);
}
public static <T> R<T> ok(T data) {
return restResult(data, 0, null);
}
public static <T> R<T> ok(T data, String msg) {
return restResult(data, 0, msg);
}
public static <T> R<T> failed() {
return restResult(null, 1, null);
}
public static <T> R<T> failed(String msg) {
return restResult(null, 1, msg);
}
public static <T> R<T> failed(T data) {
return restResult(data, 1, null);
}
public static <T> R<T> failed(T data, String msg) {
return restResult(data, 1, msg);
}
private static <T> R<T> restResult(T data, int code, String msg) {
R<T> apiResult = new R<>();
apiResult.setCode(code);
apiResult.setData(data);
apiResult.setMsg(msg);
return apiResult;
}
}
ResourceServerConfiguration
private final CustomOAuth2AuthenticationEntryPoint auth2AuthenticationEntryPoint;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
// 设置资源服务器的资源列表
resources.resourceId("resource");
// 设置资源服务器的token存储
resources.tokenStore(tokenStore);
// 针对资源服务器的异常处理
resources.authenticationEntryPoint(auth2AuthenticationEntryPoint);
}
总结
● 当访问未纳入Oauth2保护资源或者访问授权端点时客户端验证失败,抛出异常,AuthenticationEntryPoint. Commence(…)就会被调用。这个对应的代码在ExceptionTranslationFilter中,当ExceptionTranslationFilter catch到异常后,就会间接调用AuthenticationEntryPoint。默认使用LoginUrlAuthenticationEntryPoint处理异常,当抛出依次LoginUrlAuthenticationEntryPoint会将异常呈现给授权服务器默认的Login视图。
● 当访问未纳入Oauth2资源管理的接口时,因为应用接入安全框架,因此依旧会进行权限验证,当用户无权访问时会有ExceptionTranslationFilter 拦截异常并将异常呈现到默认的登录视图提示用户登录:
● 当调用授权端点(/oauth/token)时,根据前面的源码我们知道在授权认证前,会先通过客户端验证Filter进行客户端验证,当客户端验证失败会抛出异常并由ExceptionTranslationFilter 拦截,将异常呈现给默认的登录视图:spring:
redis:
host: 127.0.0.1
port: 6379
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。