首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用@ExceptionHandler处理春季安全身份验证异常

使用@ExceptionHandler处理春季安全身份验证异常
EN

Stack Overflow用户
提问于 2013-11-04 11:58:58
回答 14查看 180.4K关注 0票数 135

我使用Spring的@ControllerAdvice@ExceptionHandler来处理REST的所有例外。它适用于由web控制器引发的异常,但对于由spring安全自定义筛选器引发的异常不起作用,因为它们在调用控制器方法之前运行。

我有一个自定义的spring安全过滤器,用于基于令牌的auth:

代码语言:javascript
复制
public class AegisAuthenticationFilter extends GenericFilterBean {

...

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        try {

            ...         
        } catch(AuthenticationException authenticationException) {

            SecurityContextHolder.clearContext();
            authenticationEntryPoint.commence(request, response, authenticationException);

        }

    }

}

使用此自定义入口点:

代码语言:javascript
复制
@Component("restAuthenticationEntryPoint")
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint{

    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());
    }

}

使用这个类来全局处理异常:

代码语言:javascript
复制
@ControllerAdvice
public class RestEntityResponseExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler({ InvalidTokenException.class, AuthenticationException.class })
    @ResponseStatus(value = HttpStatus.UNAUTHORIZED)
    @ResponseBody
    public RestError handleAuthenticationException(Exception ex) {

        int errorCode = AegisErrorCode.GenericAuthenticationError;
        if(ex instanceof AegisException) {
            errorCode = ((AegisException)ex).getCode();
        }

        RestError re = new RestError(
            HttpStatus.UNAUTHORIZED,
            errorCode, 
            "...",
            ex.getMessage());

        return re;
    }
}

我需要做的是返回一个详细的JSON主体,即使是spring安全AuthenticationException。有没有办法让security和spring @ExceptionHandler一起工作?

我正在使用SpringSecurity3.1.4和spring 3.2.4。

EN

回答 14

Stack Overflow用户

回答已采纳

发布于 2013-11-05 12:11:24

好的,我试着按照建议从AuthenticationEntryPoint中自己编写json,它可以工作。

只是为了测试,我删除了AutenticationEntryPoint,从而改变了response.sendError

代码语言:javascript
复制
@Component("restAuthenticationEntryPoint")
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint{

    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException {
    
        response.setContentType("application/json");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.getOutputStream().println("{ \"error\": \"" + authenticationException.getMessage() + "\" }");

    }
}

这样,即使您使用Security,也可以发送自定义json数据和401未经授权的数据。

显然,您不会像我为测试目的所做的那样构建json,但是您将序列化一些类实例。

在Spring中,应该将其添加到http.authenticationEntryPoint()部分SecurityConfiguration文件中。

票数 83
EN

Stack Overflow用户

发布于 2017-10-02 16:51:14

我发现最好的方法是将异常委托给HandlerExceptionResolver

代码语言:javascript
复制
@Component("restAuthenticationEntryPoint")
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Autowired
    private HandlerExceptionResolver resolver;

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

然后,您可以使用@ExceptionHandler以所需的方式格式化响应。

票数 43
EN

Stack Overflow用户

发布于 2014-10-22 07:29:38

这是一个非常有趣的问题,Security和Spring框架在处理响应的方式上并不完全一致。我认为它必须以一种方便的方式支持使用MessageConverter进行错误消息处理。

我试图找到一种优雅的方法将MessageConverter注入Security,以便它们能够捕获异常并根据内容协商以正确的格式返回它们。不过,下面的解决方案并不优雅,但至少使用了Spring代码。

我假设您知道如何包含Jackson和JAXB库,否则就没有必要继续下去了。总共有三个步骤。

步骤1-创建独立类,存储MessageConverters

这门课没有魔法。它只是存储消息转换器和处理器RequestResponseBodyMethodProcessor。神奇之处在于处理器内部,它将完成所有工作,包括内容协商和相应地转换响应体。

代码语言:javascript
复制
public class MessageProcessor { // Any name you like
    // List of HttpMessageConverter
    private List<HttpMessageConverter<?>> messageConverters;
    // under org.springframework.web.servlet.mvc.method.annotation
    private RequestResponseBodyMethodProcessor processor;

    /**
     * Below class name are copied from the framework.
     * (And yes, they are hard-coded, too)
     */
    private static final boolean jaxb2Present =
        ClassUtils.isPresent("javax.xml.bind.Binder", MessageProcessor.class.getClassLoader());

    private static final boolean jackson2Present =
        ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", MessageProcessor.class.getClassLoader()) &&
        ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", MessageProcessor.class.getClassLoader());

    private static final boolean gsonPresent =
        ClassUtils.isPresent("com.google.gson.Gson", MessageProcessor.class.getClassLoader());

    public MessageProcessor() {
        this.messageConverters = new ArrayList<HttpMessageConverter<?>>();

        this.messageConverters.add(new ByteArrayHttpMessageConverter());
        this.messageConverters.add(new StringHttpMessageConverter());
        this.messageConverters.add(new ResourceHttpMessageConverter());
        this.messageConverters.add(new SourceHttpMessageConverter<Source>());
        this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());

        if (jaxb2Present) {
            this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
        }
        if (jackson2Present) {
            this.messageConverters.add(new MappingJackson2HttpMessageConverter());
        }
        else if (gsonPresent) {
            this.messageConverters.add(new GsonHttpMessageConverter());
        }

        processor = new RequestResponseBodyMethodProcessor(this.messageConverters);
    }

    /**
     * This method will convert the response body to the desire format.
     */
    public void handle(Object returnValue, HttpServletRequest request,
        HttpServletResponse response) throws Exception {
        ServletWebRequest nativeRequest = new ServletWebRequest(request, response);
        processor.handleReturnValue(returnValue, null, new ModelAndViewContainer(), nativeRequest);
    }

    /**
     * @return list of message converters
     */
    public List<HttpMessageConverter<?>> getMessageConverters() {
        return messageConverters;
    }
}

步骤2-创建AuthenticationEntryPoint

与许多教程一样,这个类对于实现自定义错误处理是必不可少的。

代码语言:javascript
复制
public class CustomEntryPoint implements AuthenticationEntryPoint {
    // The class from Step 1
    private MessageProcessor processor;

    public CustomEntryPoint() {
        // It is up to you to decide when to instantiate
        processor = new MessageProcessor();
    }

    @Override
    public void commence(HttpServletRequest request,
        HttpServletResponse response, AuthenticationException authException)
        throws IOException, ServletException {

        // This object is just like the model class, 
        // the processor will convert it to appropriate format in response body
        CustomExceptionObject returnValue = new CustomExceptionObject();
        try {
            processor.handle(returnValue, request, response);
        } catch (Exception e) {
            throw new ServletException();
        }
    }
}

步骤3-注册入口点

如前所述,我是用Java来完成的。我只是在这里展示了相关的配置,应该还有其他配置,如会话无状态等。

代码语言:javascript
复制
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling().authenticationEntryPoint(new CustomEntryPoint());
    }
}

尝试在一些身份验证失败的情况下,请记住请求头应该包括Accept : XXX,您应该得到JSON、XML或其他一些格式的异常。

票数 40
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/19767267

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档