Spring Security ExceptionTranslationFilter does not handle thrown in custom filter exception

pwuypxnk  于 5个月前  发布在  Spring
关注(0)|答案(2)|浏览(80)

我有下一个Spring Security配置:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {

private final JwtAuthenticationFilter jwtAuthFilter;
private final JwtAuthenticationEntryPoint jwtFilterAuthenticationEntryPoint;
private final AuthzAccessDeniedHandler authzAccessDeniedHandler;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
            .authorizeHttpRequests(authorize -> authorize
                    .requestMatchers(USER_PATH + USER_ID_PATH_VAR).authenticated()
                    .requestMatchers(HttpMethod.POST, EVENT_PATH).hasRole("ORGANIZER")
                    .requestMatchers(HttpMethod.GET, TICKET_PATH).authenticated()
                    .requestMatchers(AUTH_PATH + "/logout/{userUuid}").authenticated()
                    .anyRequest().permitAll()
            )
            .sessionManagement(session -> session
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
            .cors(Customizer.withDefaults())
            .exceptionHandling(handler -> handler
                    .authenticationEntryPoint(jwtFilterAuthenticationEntryPoint)
                    .accessDeniedHandler(authzAccessDeniedHandler)
            )
            .csrf(AbstractHttpConfigurer::disable)
            .httpBasic(AbstractHttpConfigurer::disable)
            .formLogin(AbstractHttpConfigurer::disable)
            .logout(AbstractHttpConfigurer::disable)
    ;

    return http.build();
}

//other omited code...

字符串
以下是JWT filter代码:

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final String AUTHZ_HEADER_PREFIX = "Bearer ";

private final JwtService jwtService;

@Override
protected void doFilterInternal(
        @NonNull HttpServletRequest request,
        @NonNull HttpServletResponse response,
        @NonNull FilterChain filterChain
) throws ServletException, IOException {
    String authzHeader = request.getHeader("Authorization");

    if (authzHeader == null || !authzHeader.startsWith(AUTHZ_HEADER_PREFIX)) {
        filterChain.doFilter(request, response);
        return;
    }

    AccessToken accessToken = jwtService.parseAccessToken(
            authzHeader.substring(AUTHZ_HEADER_PREFIX.length())
    );
    authenticateIfCurrentlyNot(accessToken, request);

    filterChain.doFilter(request, response);
}

private void authenticateIfCurrentlyNot(AccessToken accessToken, HttpServletRequest request) {
    if (SecurityContextHolder.getContext().getAuthentication() == null) {
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
                accessToken.claims().sub(),
                null,
                List.of(convertRoleToSimpleGrantedAuthority(
                        accessToken.claims().role()
                ))
        );
        token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

        SecurityContextHolder.getContext().setAuthentication(token);
    }
}

private SimpleGrantedAuthority convertRoleToSimpleGrantedAuthority(UserRole role) {
    return new SimpleGrantedAuthority("ROLE_" + role.name());
}
}


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

public class InvalidTokenException extends AuthenticationException {

public InvalidTokenException(String message) {
    super(message);
}

public static String errorCode() {
    return "invalid.token";
}
}

@Component
@Slf4j
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

@Autowired
@Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;

@Override
public void commence(
        HttpServletRequest request,
        HttpServletResponse response,
        AuthenticationException authException
) {
    log.error(authException.getMessage(), authException);
    resolver.resolveException(request, response, null, authException);
}
}


正如你所看到的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
例如:

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

字符串

选项二:

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

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


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

// JwtAuthenticationEntryPoint
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        log.error("coming to JwtAuthenticationEntryPoint");
        // Sending a custom error code and message
        var errorMessage = """
            {
                "error" : "true",
                "code" : "AUTH-1210",
                "message" : "jwt filter authentication failed"
            }
        """;
        response.sendError(HttpServletResponse.SC_FORBIDDEN, errorMessage);
    }
}

// JwtAuthenticationFilter
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private AuthenticationEntryPoint authenticationEntryPoint;

    public JwtAuthenticationFilter(AuthenticationEntryPoint authenticationEntryPoint) {
        this.authenticationEntryPoint = authenticationEntryPoint;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            throw new InvalidTokenException("error in JwtAuthenticationFilter");
        } catch (InvalidTokenException e) {
            authenticationEntryPoint.commence(request, response, e);
        }
    }

    private static final class InvalidTokenException extends AuthenticationException {
        public InvalidTokenException(String msg) {
            super(msg);
        }
    }
}


更新SecurityFilterChain配置如下:

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


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

选项三:

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

// JwtAuthenticationFilter
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            throw new InvalidTokenException("error in JwtAuthenticationFilter");
        } catch (InvalidTokenException e) {
            var errorMessage = """
                {
                    "error" : "true",
                    "code" : "AUTH-1210",
                    "message" : "jwt filter authentication failed"
                }
            """;
            response.sendError(HttpServletResponse.SC_FORBIDDEN, errorMessage);
        }
    }

    private static final class InvalidTokenException extends AuthenticationException {
        public InvalidTokenException(String msg) {
            super(msg);
        }
    }
}


更新SecurityFilterChain配置:

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


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

6uxekuva

6uxekuva2#

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

.addFilterBefore(jwtAuthFilter, AuthorizationFilter.class)

字符串

相关问题