如何处理Spring Security中过滤器抛出的自定义异常

3

我是Spring Security的新手。

我有一段代码,用于检查请求中是否传递了Authorization头,并且如果缺少该头,则抛出异常。

public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private static final String BEARER = "Bearer";

    public TokenAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
        super(requiresAuthenticationRequestMatcher);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException {
        String username = request.getParameter("username");
        String authorization = request.getHeader("AUTHORIZATION");

        if (!request.getRequestURI().equals(UniversalConstants.LOGIN_PATH)) {
            if (authorization == null || authorization.length() == 0 || !authorization.startsWith(BEARER)) {
                throw new InvalidCredentialsException("Missing authentication token"); //<-----------------
            }

        }

        String password = request.getParameter("password");
        return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(username, password));
    }

我希望能够全局处理所有异常,因此我使用了@ControllerAdvice。
注意:我知道@ControllerAdvice无法处理控制器之外抛出的异常,关于这个问题,可以参考这篇文章这篇文章中的建议。
RestAuthenticationEntryPoint.java
@Component("restAuthenticationEntryPoint")
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {

    public RestAuthenticationEntryPoint() {
        System.out.println("RestAuthenticationEntryPoint");
    }

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

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        resolver.resolveException(request, response, null, authException);

    }

}

这是我配置身份验证入口点的方法:
@Override
protected void configure(HttpSecurity http) throws Exception {
  
    http.exceptionHandling().authenticationEntryPoint(new RestAuthenticationEntryPoint()).and().cors().and().csrf().disable().exceptionHandling().defaultAuthenticationEntryPointFor(new RestAuthenticationEntryPoint(), PROTECTED_URLS)
            .and().authenticationProvider(customAuthenticationProvider())
            .addFilterBefore(tokenAuthenticationFilter(), AnonymousAuthenticationFilter.class).authorizeRequests()
            .requestMatchers(PROTECTED_URLS).authenticated().and().formLogin().disable().httpBasic().disable();

}

CustomExceptionHandler.java

@ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler({InvalidCredentialsException.class, AuthenticationException.class})
    public ResponseEntity<ErrorResponse> handleUnauthorizedError(InvalidCredentialsException e, WebRequest request) {
        String errorMessage = e.getLocalizedMessage();
        ErrorResponse errorResponse = new ErrorResponse(errorMessage, null);
        return new ResponseEntity<>(errorResponse, HttpStatus.UNAUTHORIZED);
    }
}

InvalidCredentialsException.java

@ResponseStatus(HttpStatus.UNAUTHORIZED)
public class InvalidCredentialsException extends RuntimeException {

    public InvalidCredentialsException(String errorMessage) {
        super(errorMessage);
    }

}

在调试过程中,我发现RestAuthenticationEntryPoint中的resolver.resolveException(...)CustomExceptionHandler中的handleUnauthorizedError(..)从未被调用。
我希望以优雅的方式处理throw new InvalidCredentialsException("Missing authentication token")并在响应中显示体面的JSON输出。任何帮助将不胜感激。
编辑:堆栈跟踪
2021-05-20 17:41:29.985 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/public/**']
2021-05-20 17:41:29.986 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/user/hello'; against '/public/**'
2021-05-20 17:41:29.986 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher  : No matches found
2021-05-20 17:41:29.986 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/error**']
2021-05-20 17:41:29.986 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/user/hello'; against '/error**'
2021-05-20 17:41:29.986 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher  : No matches found
2021-05-20 17:41:29.986 DEBUG 24808 --- [nio-8181-exec-3] o.s.security.web.FilterChainProxy        : /user/hello?username=user&password=user at position 1 of 12 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2021-05-20 17:41:29.988 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'GET /user/hello' doesn't match 'DELETE /logout'
2021-05-20 17:41:29.988 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher  : No matches found
2021-05-20 17:41:29.988 DEBUG 24808 --- [nio-8181-exec-3] o.s.security.web.FilterChainProxy        : /user/hello?username=user&password=user at position 6 of 12 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
2021-05-20 17:41:29.989 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.w.s.HttpSessionRequestCache        : saved request doesn't match
2021-05-20 17:41:29.989 DEBUG 24808 --- [nio-8181-exec-3] o.s.security.web.FilterChainProxy        : /user/hello?username=user&password=user at position 7 of 12 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
2021-05-20 17:41:29.989 DEBUG 24808 --- [nio-8181-exec-3] o.s.security.web.FilterChainProxy        : /user/hello?username=user&password=user at position 8 of 12 in additional filter chain; firing Filter: 'TokenAuthenticationFilter'
2021-05-20 17:41:29.989 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/public/**']
2021-05-20 17:41:29.989 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/user/hello'; against '/public/**'
2021-05-20 17:41:29.989 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher  : No matches found
2021-05-20 17:41:29.989 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.w.u.matcher.NegatedRequestMatcher  : matches = true
2021-05-20 17:41:38.030 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@7fb6b4e0
2021-05-20 17:41:38.030 DEBUG 24808 --- [nio-8181-exec-3] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2021-05-20 17:41:38.030 DEBUG 24808 --- [nio-8181-exec-3] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2021-05-20 17:41:38.033 ERROR 24808 --- [nio-8181-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception

com.spring.fieldSecurity.Exceptions.InvalidCredentialsException: Missing authentication token
    at com.spring.fieldSecurity.Service.TokenAuthenticationFilter.attemptAuthentication(TokenAuthenticationFilter.java:44) ~[classes/:na]
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-5.3.3.RELEASE.jar:5.3.3.RELEASE]
.
.   // more error trace here
.
2021-05-20 17:41:38.034 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/public/**']
2021-05-20 17:41:38.034 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/error'; against '/public/**'
2021-05-20 17:41:38.034 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher  : No matches found
2021-05-20 17:41:38.034 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher  : Trying to match using Ant [pattern='/error**']
2021-05-20 17:41:38.034 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/error'; against '/error**'
2021-05-20 17:41:38.034 DEBUG 24808 --- [nio-8181-exec-3] o.s.s.web.util.matcher.OrRequestMatcher  : matched
2021-05-20 17:41:38.034 DEBUG 24808 --- [nio-8181-exec-3] o.s.security.web.FilterChainProxy        : /error?username=user&password=user has an empty filter list
2021-05-20 17:41:38.034 DEBUG 24808 --- [nio-8181-exec-3] o.s.web.servlet.DispatcherServlet        : "ERROR" dispatch for GET "/error?username=user&password=user", parameters={masked}
2021-05-20 17:41:38.035 DEBUG 24808 --- [nio-8181-exec-3] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
2021-05-20 17:41:38.035 DEBUG 24808 --- [nio-8181-exec-3] o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
2021-05-20 17:41:38.724 DEBUG 24808 --- [nio-8181-exec-3] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Using 'application/json', given [application/json] and supported [application/json, application/*+json, application/json, application/*+json]
2021-05-20 17:41:38.724 DEBUG 24808 --- [nio-8181-exec-3] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Writing [{timestamp=Thu May 20 17:41:38 IST 2021, status=500, error=Internal Server Error, message=, path=/us (truncated)...]
2021-05-20 17:41:38.726 DEBUG 24808 --- [nio-8181-exec-3] o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
2021-05-20 17:41:38.727 DEBUG 24808 --- [nio-8181-exec-3] o.s.web.servlet.DispatcherServlet        : Exiting from "ERROR" dispatch, status 500
1个回答

4
Spring Security有一个过滤器,称为ExceptionTranslationFilter,它将AccessDeniedExceptionAuthenticationException翻译成响应。这个过滤器在Spring Security过滤器链中捕获这些抛出的异常。
因此,如果您想返回自定义异常,可以继承这些类之一,而不是RuntimeException,并添加自定义消息。
我只想强调一下,从安全的角度来看,在生产应用程序中提供友好的错误消息,特别是在身份验证/授权方面,通常是不好的做法。这些类型的消息可能会使恶意用户受益,当他们尝试某些操作时,他们会意识到自己做错了什么,并引导他们进行黑客攻击。
在测试环境中提供友好的消息可能没问题,但请确保在生产环境中将其禁用。在生产环境中,所有身份验证失败的尝试建议返回401状态码,不提供其他信息。在图形化客户端中,应显示一般性错误消息,例如“认证失败”,不提供具体信息。
另外:
编写自定义安全性实现通常也是不良实践。Spring Security经过了数以万计的应用程序在生产环境中的测试。一般情况下,不需要编写自定义过滤器来处理令牌和密码。Spring Security已经实现了使用标准(如BASIC认证和TOKEN / JWT)处理安全性和身份验证的过滤器。如果您实现了一个非标准的登录方式,一个漏洞可能会使您的应用程序面临巨大风险。
链接: - Spring中的用户名和密码身份验证 - Spring中的Oauth2身份验证

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接