Spring安全过滤器链是一个非常复杂且灵活的引擎。
链中的关键过滤器为(按顺序)
- SecurityContextPersistenceFilter(从JSESSIONID恢复身份验证)
- UsernamePasswordAuthenticationFilter(执行身份验证)
- ExceptionTranslationFilter(从FilterSecurityInterceptor捕获安全异常)
- FilterSecurityInterceptor(可能会抛出身份验证和授权异常)
查看当前的稳定发行版4.2.1文档的13.3节“ 过滤器订购”,您可以看到整个过滤器链的过滤器组织:
13.3过滤器订购
链中定义过滤器的顺序非常重要。不管您实际使用哪个过滤器,其顺序应如下:
ChannelProcessingFilter,因为它可能需要重定向到其他协议
SecurityContextPersistenceFilter,因此可以在Web请求开始时在SecurityContextHolder中设置SecurityContext,并且在Web请求结束时(可以与下一个Web请求一起使用)可以将对SecurityContext的任何更改复制到HttpSession中。
ConcurrentSessionFilter,因为它使用SecurityContextHolder功能,并且需要更新SessionRegistry以反映来自主体的持续请求
身份验证处理机制
-UsernamePasswordAuthenticationFilter, CasAuthenticationFilter,
BasicAuthenticationFilter一样等-这样SecurityContextHolder可被修饰以包含有效的认证请求令牌
该SecurityContextHolderAwareRequestFilter,如果你使用它来安装一个Spring安全意识了HttpServletRequestWrapper到你的servlet容器
该JaasApiIntegrationFilter,如果JaasAuthenticationToken在SecurityContextHolder中,这将处理FilterChain作为JaasAuthenticationToken主题
RememberMeAuthenticationFilter,因此,如果没有早期的身份验证处理机制更新SecurityContextHolder,并且该请求显示一个cookie来启用“记住我”服务,则会在此处放置一个合适的记住的Authentication对象
AnonymousAuthenticationFilter,因此,如果没有早期的身份验证处理机制更新SecurityContextHolder,则将在此处放置一个匿名Authentication对象
ExceptionTranslationFilter,以捕获任何Spring Security异常,以便可以返回HTTP错误响应或可以启动适当的AuthenticationEntryPoint
FilterSecurityInterceptor,用于保护Web URI并在拒绝访问时引发异常
现在,我将尝试一一解答您的问题:
我很困惑如何使用这些过滤器。是否是在春季提供的表单登录中,UsernamePasswordAuthenticationFilter仅用于/ login,而后面的过滤器却没有?表单登录名称空间元素是否自动配置这些过滤器?是否每个请求(无论是否经过身份验证)都到达非登录URL的FilterSecurityInterceptor?
配置完 <security-http>
部分,对于每个部分,您至少必须提供一种身份验证机制。这必须是与我刚刚参考的Spring Security文档中13.3 Filter Ordering部分中的第4组匹配的过滤器之一。
这是可以配置的最低有效security:http元素:
<security:http authentication-manager-ref="mainAuthenticationManager"
entry-point-ref="serviceAccessDeniedHandler">
<security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
</security:http>
只需执行以下操作,即可在过滤器链代理中配置以下过滤器:
{
"1": "org.springframework.security.web.context.SecurityContextPersistenceFilter",
"2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter",
"3": "org.springframework.security.web.header.HeaderWriterFilter",
"4": "org.springframework.security.web.csrf.CsrfFilter",
"5": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter",
"6": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter",
"7": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter",
"8": "org.springframework.security.web.session.SessionManagementFilter",
"9": "org.springframework.security.web.access.ExceptionTranslationFilter",
"10": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor"
}
注意:我通过创建一个简单的RestController来获得它们,该控制器@Autowires FilterChainProxy并返回其内容:
@Autowired
private FilterChainProxy filterChainProxy;
@Override
@RequestMapping("/filterChain")
public @ResponseBody Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){
return this.getSecurityFilterChainProxy();
}
public Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){
Map<Integer, Map<Integer, String>> filterChains= new HashMap<Integer, Map<Integer, String>>();
int i = 1;
for(SecurityFilterChain secfc : this.filterChainProxy.getFilterChains()){
//filters.put(i++, secfc.getClass().getName());
Map<Integer, String> filters = new HashMap<Integer, String>();
int j = 1;
for(Filter filter : secfc.getFilters()){
filters.put(j++, filter.getClass().getName());
}
filterChains.put(i++, filters);
}
return filterChains;
}
在这里我们可以看到,仅通过声明<security:http>
一个最低配置元素就包括了所有默认过滤器,但是它们都不是Authentication类型的(13.3 Filter Ordering部分中的第4组)。因此,这实际上意味着仅通过声明security:http
元素,就可以自动配置SecurityContextPersistenceFilter,ExceptionTranslationFilter和FilterSecurityInterceptor。
实际上,应该配置一种身份验证处理机制,甚至安全名称空间bean也要为此处理声明,从而在启动时抛出错误,但是可以绕过它,在其中添加entry-point-ref属性。 <http:security>
如果我<form-login>
在配置中添加基本设置,则可以这样:
<security:http authentication-manager-ref="mainAuthenticationManager">
<security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
<security:form-login />
</security:http>
现在,filterChain将如下所示:
{
"1": "org.springframework.security.web.context.SecurityContextPersistenceFilter",
"2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter",
"3": "org.springframework.security.web.header.HeaderWriterFilter",
"4": "org.springframework.security.web.csrf.CsrfFilter",
"5": "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter",
"6": "org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter",
"7": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter",
"8": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter",
"9": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter",
"10": "org.springframework.security.web.session.SessionManagementFilter",
"11": "org.springframework.security.web.access.ExceptionTranslationFilter",
"12": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor"
}
现在,这两个过滤器org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter和org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter在FilterChainProxy中创建和配置。
因此,现在的问题是:
是否是在春季提供的表单登录中,UsernamePasswordAuthenticationFilter仅用于/ login,而后面的过滤器却没有?
是的,它用于尝试完成登录处理机制,以防请求与UsernamePasswordAuthenticationFilter URL匹配。可以配置甚至更改此url的行为以匹配每个请求。
您也可能在同一FilterchainProxy中配置了多个身份验证处理机制(例如HttpBasic,CAS等)。
表单登录名称空间元素是否自动配置这些过滤器?
不,form-login元素配置UsernamePasswordAUthenticationFilter,并且如果您不提供登录页面网址,它还会配置org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter,以简单的自动生成的登录名结尾页。
默认情况下,仅创建<security:http>
没有security:"none"
属性的元素即可自动配置其他过滤器。
是否每个请求(无论是否经过身份验证)都到达非登录URL的FilterSecurityInterceptor?
每个请求都应该到达它,因为它是一个元素,它负责处理请求是否有权访问所请求的url。但是,之前处理过的某些过滤器可能会停止过滤链处理,只是不调用FilterChain.doFilter(request, response);
。例如,如果请求没有csrf参数,则CSRF筛选器可能会停止筛选器链处理。
如果我想通过从登录名中检索的JWT令牌保护我的REST API,该怎么办?我必须配置两个名称空间配置http标记,对吗?另一个用于/ login UsernamePasswordAuthenticationFilter
,另一个用于REST URL,带有custom JwtAuthenticationFilter
。
不,您没有被迫这样做。您可以在同一个http元素中声明UsernamePasswordAuthenticationFilter
和JwtAuthenticationFilter
,但这取决于每个过滤器的具体行为。两种方法都是可行的,最后选择哪种方法取决于自己的偏好。
配置两个http元素会创建两个springSecurityFitlerChains吗?
是的,这是真的
在我声明表单登录之前,UsernamePasswordAuthenticationFilter是否默认关闭?
是的,您可以在我发布的每个配置中引发的过滤器中看到它
如何用一个替换SecurityContextPersistenceFilter,它将从现有的JWT令牌而不是JSESSIONID获得身份验证?
你可以避开SecurityContextPersistenceFilter,只是配置会话策略在<http:element>
。只需这样配置:
<security:http create-session="stateless" >
或者,在这种情况下,您可以用另一个过滤器覆盖它,方法是在<security:http>
元素内:
<security:http ...>
<security:custom-filter ref="myCustomFilter" position="SECURITY_CONTEXT_FILTER"/>
</security:http>
<beans:bean id="myCustomFilter" class="com.xyz.myFilter" />
编辑:
一个有关“您可能在同一个FilterchainProxy中配置多个身份验证处理机制”的问题。如果声明多个(Spring实现)身份验证过滤器,后者会覆盖第一个身份验证执行的身份验证吗?这与拥有多个身份验证提供者有何关系?
最后,这取决于每个过滤器本身的实现,但事实是,后一个身份验证过滤器至少能够覆盖由先前过滤器最终进行的任何先前身份验证,这是事实。
但这不一定会发生。我在安全的REST服务中有一些生产案例,其中我使用一种授权令牌,可以将其作为Http标头或在请求正文中提供。因此,我配置了两个过滤器来恢复该令牌,一种情况是从Http标头中恢复,另一种是从自己的其余请求的请求正文中恢复。的事实是,如果一个http请求同时提供身份验证令牌作为Http标头和请求主体内部,则两个过滤器都将尝试执行将身份验证令牌委派给管理器的身份验证机制,但是只需检查请求是否为仅在doFilter()
每个过滤器方法开始时就已通过身份验证。
具有多个身份验证过滤器与具有多个身份验证提供程序有关,但不要强行使用。在我之前公开的情况下,我有两个身份验证过滤器,但我只有一个身份验证提供程序,因为两个过滤器都创建相同类型的身份验证对象,因此在两种情况下,身份验证管理器都将其委托给相同的提供程序。
与此相反,我也有一种情况,我只发布一个UsernamePasswordAuthenticationFilter,但是用户凭据都可以包含在DB或LDAP中,因此我有两个UsernamePasswordAuthenticationToken支持提供者,而AuthenticationManager将来自过滤器的任何身份验证尝试委托给提供者安全地验证凭据。
因此,我认为很明显,身份验证筛选器的数量不能确定身份验证提供程序的数量,也不能由提供者的数量确定筛选器的数量。
此外,文档指出SecurityContextPersistenceFilter负责清理SecurityContext,这对线程池很重要。如果我省略它或提供自定义实现,则必须手动执行清理,对吗?定制链时还有更多类似的陷阱吗?
之前,我没有仔细研究过此过滤器,但是在您问完最后一个问题之后,我一直在检查它的实现,就像通常在Spring中一样,几乎所有东西都可以配置,扩展或覆盖。
该SecurityContextPersistenceFilter在代表SecurityContextRepository执行搜索SecurityContext中。默认情况下,使用HttpSessionSecurityContextRepository,但是可以使用过滤器的构造函数之一进行更改。因此,最好编写一个满足您需要的SecurityContextRepository并仅在SecurityContextPersistenceFilter中对其进行配置,并相信它已被证明的行为,而不是从头开始。