【问题标题】:Spring REST/MVC & Security exception handling & filter chain requirementsSpring REST/MVC & 安全异常处理 & 过滤器链需求
【发布时间】:2014-05-20 03:59:21
【问题描述】:

我有一个使用 Spring Security 的 Spring REST 应用程序。到目前为止,我一直在使用 @ControllerAdvice 类进行错误处理,该类非常适合处理 MVC 过程中引发的所有错误。

但是,Spring Security 抛出的所有异常(例如:AccessDeniedException)都不会被 MVC 错误处理处理,因为它们实际上从未到达 MVC 组件。因此,如果我想处理 Spring Security 异常,我必须添加 AccessDeniedHandler 或使用放置在 EXCEPTION_TRANSLATION_FILTER 之后的自定义过滤器(请参阅 Spring Docs and Filter Ordering)。

我处理我的异常的主要目标是,我想向客户端返回一致的响应,无论抛出错误并嵌入在 JSON 对象中的相关信息(例如:时间戳、错误代码、消息等)。

这种设置是否有首选技术?还是能够集中所有内容的更好方法?我喜欢使用 @ExceptionHandlers 处理 MVC 错误的简单性,但似乎一切都促使我使用自定义过滤器并在一个位置处理所有内容。

在相关说明中,在我看来,使用命名空间配置生成的 default FilterChain 对于 REST 应用程序来说是多余的。是否有更适合 REST 应用程序的配置? (例如:不需要 FORM_LOGIN_FILTER、REMEMBER_ME_FILTER、CONCURRENT_SESSION_FILTER 等...)。有没有人列出应该在 REST 应用程序中使用的过滤器?我在文档中找不到任何内容。

【问题讨论】:

    标签: java spring rest exception-handling spring-security


    【解决方案1】:

    我不确定我是否理解正确。我也有一个问题,将带有 json 的正确错误消息发送回客户端。我已经编写了所有(过滤器和提供者)自定义,因为我通过 HTTPHeaderFields 进行身份验证。 错误处理和将正确的消息发送回客户端一直是最大的问题。简要概述如下:

    起初我创建了自定义 delegatingAuthenticationEntryPoint 和 AccesssDeniedHandler。
    安全配置片段

    ...
    @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .csrf().disable()
                // IMPORTANT: Add Filter after "ExceptionTranslation". 
                // If not AuthenticationException from Custom Filter or Custom Provider
                // will not be catched by AuthenticationEntryPoint.
                .addFilterAfter(httpClientFilter(), ExceptionTranslationFilter.class)
                .exceptionHandling()
                    // catch AuthenticationExeption and SecureToken with authenticated=false
                    .authenticationEntryPoint(delegatingAuthenticationEntryPoint())
                    // catch PermissionDenied Exeption e.g. missing in authorizeRequests()
                    .accessDeniedHandler(new ClientRestAccessDeniedHandler())
                    .and()
    ...
    

    ClientRestAccessDeniedHandler 看起来像这样

    public class ClientRestAccessDeniedHandler implements AccessDeniedHandler{
    
        @Override
        public void handle(HttpServletRequest request,  HttpServletResponse response,
                AccessDeniedException accessDeniedException) throws IOException, ServletException {
    
            final Logger logger = Logger.getLogger(ClientRestAccessDeniedHandler.class);
    
            if(logger.isDebugEnabled())
                logger.debug("Requered Role for this request is missing!");
    
            HTTPAuthenticationErrorSender.sendResponse(request, response, 
                    SecurityContextHolder.getContext().getAuthentication());
        }
    }
    

    CustomDelegationEntriyPoint 看起来非常相似。两者都调用 HTTPAuthenticationErrorSender。它将发送由低级东西生成的 HttpResponse :-)。

    public final class HTTPAuthenticationErrorSender {
    
        public static void sendResponse(HttpServletRequest request, HttpServletResponse response, Authentication token) 
                throws JsonGenerationException, JsonMappingException, IOException{
    
            final Logger logger = Logger.getLogger(HTTPAuthenticationErrorSender.class);
    
            if(!(token instanceof HTTPRestSecureToken)){
                if (token != null){
                    response.sendError(403, "No valide AuthenticationToken found. Token instance of: "+token.getClass().toString());
                    if(logger.isDebugEnabled())
                        logger.debug("Send default HTTP Response 403. No HTTPRestSecureToken found. "
                                + "Token is instance of: "+token.getClass().getName());
                }
                else {
                    response.sendError(403, "No valide AuthenticationToken found. Token is null");
                    if(logger.isDebugEnabled())
                        logger.debug("Send default HTTP Response 403. No HTTPRestSecureToken found. "
                                + "Token is null");
                }
                return;
            }
    
            HTTPRestSecureToken restToken = (HTTPRestSecureToken) token;
            ObjectMapper mapper = new ObjectMapper();
            AuthenticationErrorResponse authErrorResponse =
                    new AuthenticationErrorResponse(restToken.getAuthStatus().getErrorCode(),restToken.getAuthStatus().getDescription());
            String content = mapper.writeValueAsString(authErrorResponse);
    
            HTTPRestPrincipal principal = (HTTPRestPrincipal) token.getPrincipal();
    
            if(logger.isDebugEnabled()){
                logger.debug("AccessDenied for request: ["+principal.getFullURI()+"] clientID: ["+principal.getClientID()
                        + "] loginMail: ["+principal.getLoginMail()+"]");
                logger.debug("Send following json response: "+content);
            }
    
            response.setContentType("application/json;charset=UTF-8");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().print(content);
        }
    
    }
    

    为了发送正确的信息,我在我的自定义 SecureToken 中跟踪状态信息。令牌将运行 throw 安全链过滤器和提供程序,并将填充信息。

    是的,我会说这对于像这样的常见问题来说并不是很小。但我找不到其他方法。默认实现。对我的情况不够详细。 我希望它会对您有所帮助或推动您朝着正确的方向前进。

    【讨论】:

    • 你的httpClientFilter()有什么?
    猜你喜欢
    • 2016-06-14
    • 2013-05-25
    • 2016-11-18
    • 2015-09-01
    • 2016-05-07
    • 2019-12-28
    • 1970-01-01
    • 2020-06-01
    • 1970-01-01
    相关资源
    最近更新 更多