Spring REST / MVC&安全异常处理&过滤链要求

时间:2014-04-07 18:36:02

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

我有一个使用Spring Security的Spring REST应用程序。到目前为止,我一直在使用@ControllerAdvice类进行错误处理,这对于处理MVC过程中抛出的所有错误非常有用。

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

我处理异常的主要目标是,无论错误是否被抛入JSON对象中嵌入的相关信息(例如:时间戳,错误代码,消息等),我都希望向客户端返回一致的响应。 / p>

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

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

1 个答案:

答案 0 :(得分:2)

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

首先我创建了自定义delegatingAuthenticationEntryPoint和AccesssDeniedHandler 安全配置的Snipp

...
@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());
    }
}

Custom DelegationEntriyPoint看起来非常相似。两者都调用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中跟踪状态信息。令牌将运行抛出安全链过滤器和提供程序,并将填充信息。

是的,我会说这对于像这样的常见问题并不是很小。但我找不到另一种方式。默认的Impl。对我的情况不够详细。 我希望它会有所帮助,或者让你朝着正确的方向前进。