我们正在使用spring MVC + spring security + hibernate创建一个RESTful API。 API可以生成JSON和HTML。 为弹簧安全做一个好的错误处理让我很头疼:
身份验证可以通过各种方式进行:BasicAuth,通过POST请求中的不同参数以及Web登录。
对于每个身份验证机制,在spring security xml config的<http>
namespace元素中声明了一个过滤器。
我们在自定义HandlerExceptionResolver
中处理所有春季异常。这适用于我们的控制器中抛出的所有异常,但我不知道如何处理自定义spring安全过滤器中引发的自定义异常。
由于Spring安全过滤器在我们调用任何控制器之前到来,因此我们没有看到我们在自定义spring安全过滤器中抛出的异常。
我在stackoverflow上找到了这个问题:
Use custom exceptions in Spring Security。但是我不明白他们在哪里处理那里抛出的异常。
我们尝试了这种方法,但我们的自定义HandlerExceptionResolver
未被调用。而是向用户呈现由tomcat呈现的丑陋堆栈跟踪。
为什么我们需要这个?
可以激活和停用用户。如果它们被停用并尝试执行某些操作,我们希望返回带有自定义错误消息的JSON。这应该与spring security抛出AccessDeniedException
时显示的不同。 AccessDeniedException
以某种方式进入我们的HandlerExceptionResolver
,但我无法完全了解。
可能的解决方案
我们考虑过使用ExceptionTranslationFilter
,但是当我们抛出自定义异常(在doFilter()方法的catch语句中设置断点)时,不会调用它。在我的理解中,应该调用这个catch块,并且应该使用一个认证入口点。
另一种可能性:我们可以在spring安全过滤器链中执行与ExceptionTranslationFilter
类似的操作,并执行类似于AccessDeniedHandler
的操作:
RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
dispatcher.forward(request, response);
我们可以在请求中添加一些参数(错误代码,原因等)并让它指向一个控制器,它将处理JSON或HTML中的渲染。
以下是我们配置的简短摘录:
Spring Security:
<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>
HandlerExceptionResolver的AppConfig
@Bean
public HandlerExceptionResolver handlerExceptionResolver(){
logger.info("creating handler exception resolver");
return new AllExceptionHandler();
}
我们的自定义HandlerExceptionResolver
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;
}
Web.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>
有没有人对如何解决这个问题有任何指示? 我查看了谷歌上的许多文章,其中大部分都描述了当没有过滤器能够对请求进行身份验证时如何处理Spring安全性抛出的AccessDeniedException。
我们正在使用Spring Security 3.1.0和spring web mvc 3.1.0。
答案 0 :(得分:9)
重要的是要记住Spring Security中过滤器的顺序很重要。
来自Spring Security 3本书:
ExceptionTranslationFilter
将能够处理和做出反应 只有那些在过滤器链中抛出它的异常 执行堆栈。用户经常会感到困惑,特别是在添加时 自定义过滤器的顺序不正确,以及为什么会出现预期的行为 与许多应用程序的实际异常处理不同 这些情况下,过滤器的顺序是罪魁祸首!
如果您的过滤器是关于授权的,那么将它们放在链的末尾是一个好习惯,因为默认授权过滤器使用此方法。这样你就不必重新发明轮子了。
标准过滤器:Table in documentation
正确配置过滤器链后,您可以配置错误页面,甚至自定义处理程序。有关详情,请参阅documentation。
答案 1 :(得分:0)
我看到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中为/ *配置过滤器),它只是尝试了catch块并将任何未捕获的异常委托给自定义异常处理程序Spring组件调用ExceptionController方法(每个方法以不同的方式返回不同的响应类型)以故障安全的方式也返回基于异常类型(我们的要求)的自定义异常消息。唯一下来的是添加一些逻辑,这样你就不会保持循环。控制器中的Spring自定义ExceptionHandlerExceptionResolver和@ExceptionHandler不处理过滤器异常,并且限制了您希望如何将异常消息作为(XML / JSON,重定向,转发......)返回。这假设您具有良好的应用程序异常层次结构,该层次结构捕获异常并使用合理的参考信息将其抛出,因为过滤器没有任何内容。
错误代码相同,在web.xml中定义静态页面,但通过将过滤器映射到ERROR调度程序并为显示错误代码的页面准备模型来捕获它们。