当前位置:   article > 正文

(七) OAuth 2.0 自定义异常处理格式_invalidgrantexception

invalidgrantexception

前言

本篇文章的代码较多,但是核心的点是: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));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    }

    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));
    
    • 1
    • 2

    }

}

/**

  • @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 的默认实现,可以去对比源码的实现。为了简洁我把很多类写在一起,实际在使用的过程中也可以拆开这些类。
  • 1

/**

  • @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);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    }
    }

@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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

}

ResourceServerConfiguration

private final CustomOAuth2AuthenticationEntryPoint auth2AuthenticationEntryPoint;

@Override
public void configure(ResourceServerSecurityConfigurer resources) {

    // 设置资源服务器的资源列表
    resources.resourceId("resource");

    // 设置资源服务器的token存储
    resources.tokenStore(tokenStore);

    // 针对资源服务器的异常处理
    resources.authenticationEntryPoint(auth2AuthenticationEntryPoint);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

总结

● 当访问未纳入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

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号