处理春季安全中的自定义异常

我们正在创建一个具有弹簧MVC + spring security + hibernate的RESTful API。该 API 可以生成 JSON 和 HTML。为弹簧安全做一个好的错误处理让我头疼:

身份验证可以通过多种方式进行:BasicAuth,通过POST请求中的不同参数以及通过Web登录。对于每个身份验证机制,在 spring 安全 xml 配置的命名空间元素中都有一个声明的筛选器。<http>

我们在自定义中处理所有弹簧异常。这适用于控制器中引发的所有异常,但我不知道如何处理自定义弹簧安全过滤器中引发的自定义异常。由于弹簧安全过滤器是在调用任何控制器之前出现的,因此我们不会在自定义弹簧安全过滤器中看到异常。HandlerExceptionResolver

我在stackoverflow上发现了这个问题:在Spring Security中使用自定义异常。但是,我不明白他们在哪里处理在那里引发的异常。我们尝试了这种方法,但没有调用我们的自定义。相反,用户会看到一个由tomcat渲染的丑陋的堆栈跟踪。HandlerExceptionResolver

我们为什么需要这个?可以激活和停用用户。如果它们被停用并尝试执行某些操作,我们希望返回带有自定义错误消息的JSON。这应该不同于春季安全性抛出.不知何故到达我们的,但我无法确切地遵循。AccessDeniedExceptionAccessDeniedExceptionHandlerExceptionResolver

可能的解决方案我们考虑过使用 ,但是当我们抛出自定义异常时,不会调用它(在 doFilter() 方法的 catch 语句中设置断点)。在我的理解中,应该调用这个捕获块,并且应该使用身份验证入口点。ExceptionTranslationFilter

另一种可能性:我们可以做一些类似于弹簧安全过滤器链的事情,并做一些类似于它所做的事情:ExceptionTranslationFilterAccessDeniedHandler

RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
dispatcher.forward(request, response);

我们可以向请求添加一些参数(错误代码,原因等),并使其指向一个控制器,该控制器将负责JSON或HTML的呈现。

以下是我们配置的简短摘录:

弹簧安全:

<http create-session="stateless" use-expressions="true" >
    <!-- Try getting the authorization object from the request parameters. -->
    <security:custom-filter ref="filter1" after="SECURITY_CONTEXT_FILTER"/>
    <security:custom-filter ref="filter2" before="LOGOUT_FILTER"/>
    <!-- Intercept certain URLS differently -->

    <intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')" />
    <!-- Some more stuff here -->
    <intercept-url pattern="/**" access="denyAll" />  
    <http-basic />
</http>

处理程序异常解析器的应用配置

@Bean
public HandlerExceptionResolver handlerExceptionResolver(){
    logger.info("creating handler exception resolver");
    return new AllExceptionHandler();
}

我们的自定义处理程序异常解决方案

public class AllExceptionHandler implements HandlerExceptionResolver {

    private static final Logger logger = LoggerFactory
        .getLogger(AppConfig.class);

    @Override
    public ModelAndView resolveException(HttpServletRequest request,
            HttpServletResponse response, Object handler, Exception ex) {
    // This is just a snipped of the real method code
    return new ModelAndView("errorPage");
}

我们其中一个过滤器的相关部分:

try {
    Authentication authResult = authenticationManger.authenticate(authRequest);
    SecurityContextHolder.getContext().setAuthentication(authResult);
}

catch(AuthenticationException failed) {
    SecurityContextHolder.clearContext();
    throw failed; 
}

网.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
    <param-name>contextClass</param-name>
    <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>xxx.xxx.xxx.config</param-value>
</context-param>
<context-param>
    <param-name>spring.profiles.default</param-name>
    <param-value>LIVE</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value></param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    <!-- Add multipart support for files up to 10 MB -->
    <multipart-config>
        <max-file-size>10000000</max-file-size>
    </multipart-config>
</servlet>
<servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
    <filter-name>openEntityManagerInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>openEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<!-- Map filters -->
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<error-page>
    <error-code>404</error-code>
    <location>/handle/404</location>
</error-page>
</web-app>

有没有人对我们如何解决这个问题有任何指示?我浏览了Google上的许多文章,其中大多数都描述了当没有过滤器能够验证请求时如何处理Spring Security抛出的AccessDeniedException。

我们使用的是Spring Security 3.1.0和spring Web mvc 3.1.0。


答案 1

重要的是要记住,Spring Security中过滤器的顺序很重要。

来自 Spring Security 3 书:

将能够处理和响应筛选器链执行堆栈中在其下方引发的那些异常。用户经常感到困惑,尤其是在以不正确的顺序添加自定义筛选器时,对于为什么预期行为与其应用程序的实际异常处理不同 - 在许多情况下,筛选器的顺序是罪魁祸首!ExceptionTranslationFilter

如果您的过滤器是关于授权的,那么将它们放在链的末端是一种很好的做法,因为默认的授权过滤器使用此方法。这样,您就不必重新发明轮子。

标准过滤器:文档中的表格

正确配置筛选器链后,可以配置错误页,甚至可以配置自定义处理程序。文档中提供了更多信息。


答案 2

我看到 ExceptionTranslationFilter 只处理两个异常 AuthenticationException 和 AccessDeniedException,并为这两个异常使用自定义处理程序,那么任何其他类型的异常甚至运行时异常呢?

您将如何处理/拦截Spring过滤器堆栈中的任何异常?难道没有任何Spring标准方法来捕获和获取请求(除了在所有内容之上编写自定义过滤器之外),响应而无需在所有内容之上编写另一个过滤器?

<security:http auto-config="false" use-expressions="true"
disable-url-rewriting="true" entry-point-ref="authenticationEntryPoint"
pattern="/**">

<security:custom-filter before="FIRST" ref="stackExceptionFilter" />

<security:custom-filter before="..." ref="authenticationFilter" />
<security:logout />
</security:http> 

好吧,我最终在顶部添加了另一个过滤器(或者在web.xml中为/*配置过滤器),它只是尝试捕获块并将任何未捕获的异常委托给自定义异常处理程序Spring组件,该组件以故障安全的方式调用异常控制器方法(每个方法以不同的方式返回不同的响应类型),并根据异常类型返回自定义异常消息(我们的要求)。唯一的缺点是添加一些逻辑,这样你就不会继续循环。控制器中的Spring自定义 ExceptionHandlerExceptionResolver和@ExceptionHandler不处理过滤器异常,并且对您希望如何返回异常消息(XML / JSON,重定向,转发,....)有限制。这假设您具有良好的应用程序异常层次结构,该层次结构捕获异常并抛出具有合理引用信息的异常,因为筛选器没有任何内容。

与错误代码一样,在 web 中定义静态页.xml但通过将筛选器映射到 ERROR 调度程序并为显示错误代码的页面准备模型来捕获它们。


推荐