Spring Security ExceptionTranslationFilter does not handle thrown in custom filter exception

pwuypxnk  于 2024-01-05  发布在  Spring
关注(0)|答案(2)|浏览(175)

我有下一个Spring Security配置:

  1. @Configuration
  2. @EnableWebSecurity
  3. @EnableMethodSecurity
  4. @RequiredArgsConstructor
  5. public class SecurityConfig {
  6. private final JwtAuthenticationFilter jwtAuthFilter;
  7. private final JwtAuthenticationEntryPoint jwtFilterAuthenticationEntryPoint;
  8. private final AuthzAccessDeniedHandler authzAccessDeniedHandler;
  9. @Bean
  10. public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  11. http
  12. .authorizeHttpRequests(authorize -> authorize
  13. .requestMatchers(USER_PATH + USER_ID_PATH_VAR).authenticated()
  14. .requestMatchers(HttpMethod.POST, EVENT_PATH).hasRole("ORGANIZER")
  15. .requestMatchers(HttpMethod.GET, TICKET_PATH).authenticated()
  16. .requestMatchers(AUTH_PATH + "/logout/{userUuid}").authenticated()
  17. .anyRequest().permitAll()
  18. )
  19. .sessionManagement(session -> session
  20. .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  21. )
  22. .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
  23. .cors(Customizer.withDefaults())
  24. .exceptionHandling(handler -> handler
  25. .authenticationEntryPoint(jwtFilterAuthenticationEntryPoint)
  26. .accessDeniedHandler(authzAccessDeniedHandler)
  27. )
  28. .csrf(AbstractHttpConfigurer::disable)
  29. .httpBasic(AbstractHttpConfigurer::disable)
  30. .formLogin(AbstractHttpConfigurer::disable)
  31. .logout(AbstractHttpConfigurer::disable)
  32. ;
  33. return http.build();
  34. }
  35. //other omited code...

字符串
以下是JWT filter代码:

  1. @Component
  2. @RequiredArgsConstructor
  3. public class JwtAuthenticationFilter extends OncePerRequestFilter {
  4. private final String AUTHZ_HEADER_PREFIX = "Bearer ";
  5. private final JwtService jwtService;
  6. @Override
  7. protected void doFilterInternal(
  8. @NonNull HttpServletRequest request,
  9. @NonNull HttpServletResponse response,
  10. @NonNull FilterChain filterChain
  11. ) throws ServletException, IOException {
  12. String authzHeader = request.getHeader("Authorization");
  13. if (authzHeader == null || !authzHeader.startsWith(AUTHZ_HEADER_PREFIX)) {
  14. filterChain.doFilter(request, response);
  15. return;
  16. }
  17. AccessToken accessToken = jwtService.parseAccessToken(
  18. authzHeader.substring(AUTHZ_HEADER_PREFIX.length())
  19. );
  20. authenticateIfCurrentlyNot(accessToken, request);
  21. filterChain.doFilter(request, response);
  22. }
  23. private void authenticateIfCurrentlyNot(AccessToken accessToken, HttpServletRequest request) {
  24. if (SecurityContextHolder.getContext().getAuthentication() == null) {
  25. UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
  26. accessToken.claims().sub(),
  27. null,
  28. List.of(convertRoleToSimpleGrantedAuthority(
  29. accessToken.claims().role()
  30. ))
  31. );
  32. token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
  33. SecurityContextHolder.getContext().setAuthentication(token);
  34. }
  35. }
  36. private SimpleGrantedAuthority convertRoleToSimpleGrantedAuthority(UserRole role) {
  37. return new SimpleGrantedAuthority("ROLE_" + role.name());
  38. }
  39. }


如果由于各种原因,给定的编码JWT无效,则方法parseToken()抛出自定义InvalidTokenException。我想处理此异常,正如您在上面的配置中看到的那样,我已经添加了名为jwtFilterAuthenticationEntryPoint的自定义AuthenticationEntryPoint。现在这里是InvalidTokenException和JwtFilterAuthenticationEntryPoint的代码:

  1. public class InvalidTokenException extends AuthenticationException {
  2. public InvalidTokenException(String message) {
  3. super(message);
  4. }
  5. public static String errorCode() {
  6. return "invalid.token";
  7. }
  8. }
  9. @Component
  10. @Slf4j
  11. public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
  12. @Autowired
  13. @Qualifier("handlerExceptionResolver")
  14. private HandlerExceptionResolver resolver;
  15. @Override
  16. public void commence(
  17. HttpServletRequest request,
  18. HttpServletResponse response,
  19. AuthenticationException authException
  20. ) {
  21. log.error(authException.getMessage(), authException);
  22. resolver.resolveException(request, response, null, authException);
  23. }
  24. }


正如你所看到的ExceptionTranslationFilter捕捉我的异常,我已经从AuthenticationException扩展了它,所以在官方文档中写的(ExceptionTranslationFilter伪代码),它将能够处理它。但是当我尝试测试JwtAuthenticationEntryPoint的commence()方法的异常处理代码时,没有执行,因为没有提供日志:

所以我期待着任何帮助解决这个问题)

5uzkadbs

5uzkadbs1#

在您的情况下,由于SecurityFilterChain中的语句addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class),在调用ExceptionTranslationFilter之前抛出异常。因此,没有调用ExceptionTranslationFilter
为了解释为什么JwtAuthenticationEntryPoint没有被触发,当AuthenticationException发生时,注册的AuthenticationEntryPointcommence方法通常由ExceptionTranslationFilter调用。
需要注意的是,过滤器可能有自己的AuthenticationEntryPoint。例如,在httpBasic的情况下,身份验证由BasicAuthenticationFilter处理,任何问题都会导致调用BasicAuthenticationEntryPoint
我可以看到3个选项来处理JwtAuthenticationFilter中的错误:

选项一:

SecurityFilterChain中的AuthorizationFilter之前或ExceptionTranslationFilter之后执行JwtAuthenticationFilter
例如:

  1. addFilterBefore(new JwtAuthenticationFilter(), AuthorizationFilter.class)
  2. // or
  3. addFilterAfter(new JwtAuthenticationFilter(), ExceptionTranslationFilter.class)

字符串

选项二:

包括在JwtAuthenticationFilter中捕获AuthenticationException并调用JwtAuthenticationEntryPointcommence方法。
为了说明这一点,让我们看看Spring BasicAuthenticationFilter如何管理AuthenticationExceptionBasicAuthenticationEntryPointcommence方法如下所示:

  1. @Override
  2. public void commence(HttpServletRequest request, HttpServletResponse response,
  3. AuthenticationException authException) throws IOException {
  4. response.setHeader("WWW-Authenticate", "Basic realm=\"" + this.realmName + "\"");
  5. response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
  6. }


在检查BasicAuthenticationEntryPoint的源代码时,很明显它试图处理AuthenticationException并随后调用authenticationEntryPoint.commence(request, response, ex)
应用类似的方法,可以对JwtAuthenticationEntryPointJwtAuthenticationFilter进行以下调整:

  1. // JwtAuthenticationEntryPoint
  2. public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
  3. @Override
  4. public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
  5. log.error("coming to JwtAuthenticationEntryPoint");
  6. // Sending a custom error code and message
  7. var errorMessage = """
  8. {
  9. "error" : "true",
  10. "code" : "AUTH-1210",
  11. "message" : "jwt filter authentication failed"
  12. }
  13. """;
  14. response.sendError(HttpServletResponse.SC_FORBIDDEN, errorMessage);
  15. }
  16. }
  17. // JwtAuthenticationFilter
  18. public class JwtAuthenticationFilter extends OncePerRequestFilter {
  19. private AuthenticationEntryPoint authenticationEntryPoint;
  20. public JwtAuthenticationFilter(AuthenticationEntryPoint authenticationEntryPoint) {
  21. this.authenticationEntryPoint = authenticationEntryPoint;
  22. }
  23. @Override
  24. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
  25. try {
  26. throw new InvalidTokenException("error in JwtAuthenticationFilter");
  27. } catch (InvalidTokenException e) {
  28. authenticationEntryPoint.commence(request, response, e);
  29. }
  30. }
  31. private static final class InvalidTokenException extends AuthenticationException {
  32. public InvalidTokenException(String msg) {
  33. super(msg);
  34. }
  35. }
  36. }


更新SecurityFilterChain配置如下:

  1. .addFilterBefore(new JwtAuthenticationFilter(new JwtAuthenticationEntryPoint()), UsernamePasswordAuthenticationFilter.class)


需要注意的是,不需要显式注册JwtAuthenticationEntryPoint

选项三:

另一种方法是直接在JwtAuthenticationFilter中处理AuthenticationException并发送错误响应。这消除了对JwtAuthenticationEntryPoint的需要。下面是修改后的JwtAuthenticationFilter

  1. // JwtAuthenticationFilter
  2. public class JwtAuthenticationFilter extends OncePerRequestFilter {
  3. @Override
  4. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
  5. try {
  6. throw new InvalidTokenException("error in JwtAuthenticationFilter");
  7. } catch (InvalidTokenException e) {
  8. var errorMessage = """
  9. {
  10. "error" : "true",
  11. "code" : "AUTH-1210",
  12. "message" : "jwt filter authentication failed"
  13. }
  14. """;
  15. response.sendError(HttpServletResponse.SC_FORBIDDEN, errorMessage);
  16. }
  17. }
  18. private static final class InvalidTokenException extends AuthenticationException {
  19. public InvalidTokenException(String msg) {
  20. super(msg);
  21. }
  22. }
  23. }


更新SecurityFilterChain配置:

  1. .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)


选择最适合您的要求并与您的身份验证流程无缝集成的选项。

展开查看全部
6uxekuva

6uxekuva2#

问题出在过滤器的顺序上。我的自定义过滤器在ExceptionTranslationFilter之前,所以当抛出异常时,ExceptionTranslationFilter无法到达。
这行代码修复了它:

  1. .addFilterBefore(jwtAuthFilter, AuthorizationFilter.class)

字符串

相关问题