Spring Security筛选器链如何工作


135

我意识到Spring安全性是建立在过滤器链上的,该过滤器将拦截请求,检测(不存在)身份验证,重定向到身份验证入口点或将请求传递给授权服务,并最终让请求到达servlet或引发安全异常(未经身份验证或未经授权)。DelegatingFitlerProxy将这些过滤器粘合在一起。为了执行任务,这些筛选器访问服务,例如UserDetailsS​​erviceAuthenticationManager

链中的关键过滤器为(按顺序)

  • SecurityContextPersistenceFilter(从JSESSIONID恢复身份验证)
  • UsernamePasswordAuthenticationFilter(执行身份验证)
  • ExceptionTranslationFilter(从FilterSecurityInterceptor捕获安全异常)
  • FilterSecurityInterceptor(可能会抛出身份验证和授权异常)

我对如何使用这些过滤器感到困惑。是否是在春季提供的表单登录中,UsernamePasswordAuthenticationFilter仅用于/ login,而后面的过滤器未使用?表单登录名称空间元素是否自动配置这些过滤器?是否每个请求(无论是否经过身份验证)都到达非登录URL的FilterSecurityInterceptor

如果我想使用从登录名检索到的JWT-token保护我的REST API ,该怎么办?我必须配置两个名称空间配置http标签,对吗?一个用于/ login,使用UsernamePasswordAuthenticationFilter,另一个用于REST URL,带有自定义JwtAuthenticationFilter

配置两个http元素会创建两个元素springSecurityFitlerChains吗?是UsernamePasswordAuthenticationFilter默认是关闭的,直到我宣布form-login?如何SecurityContextPersistenceFilterAuthentication将从现有JWT-token而不是从中获取的过滤器替换JSESSIONID


过滤器的默认顺序在org.springframework.security.config.annotation.web.builders.FilterComparator中定义
blacelle

Answers:


210

Spring安全过滤器链是一个非常复杂且灵活的引擎。

链中的关键过滤器为(按顺序)

  • SecurityContextPersistenceFilter(从JSESSIONID恢复身份验证)
  • UsernamePasswordAuthenticationFilter(执行身份验证)
  • ExceptionTranslationFilter(从FilterSecurityInterceptor捕获安全异常)
  • FilterSecurityInterceptor(可能会抛出身份验证和授权异常)

查看当前的稳定发行版4.2.1文档的13.3节“ 过滤器订购”,您可以看到整个过滤器链的过滤器组织:

13.3过滤器订购

链中定义过滤器的顺序非常重要。不管您实际使用哪个过滤器,其顺序应如下:

  1. ChannelProcessingFilter,因为它可能需要重定向到其他协议

  2. SecurityContextPersistenceFilter,因此可以在Web请求开始时在SecurityContextHolder中设置SecurityContext,并且在Web请求结束时(可以与下一个Web请求一起使用)可以将对SecurityContext的任何更改复制到HttpSession中。

  3. ConcurrentSessionFilter,因为它使用SecurityContextHolder功能,并且需要更新SessionRegistry以反映来自主体的持续请求

  4. 身份验证处理机制 -UsernamePasswordAuthenticationFilter CasAuthenticationFilter BasicAuthenticationFilter一样等-这样SecurityContextHolder可被修饰以包含有效的认证请求令牌

  5. SecurityContextHolderAwareRequestFilter,如果你使用它来安装一个Spring安全意识了HttpServletRequestWrapper到你的servlet容器

  6. JaasApiIntegrationFilter,如果JaasAuthenticationToken在SecurityContextHolder中,这将处理FilterChain作为JaasAuthenticationToken主题

  7. RememberMeAuthenticationFilter,因此,如果没有早期的身份验证处理机制更新SecurityContextHolder,并且该请求显示一个cookie来启用“记住我”服务,则会在此处放置一个合适的记住的Authentication对象

  8. AnonymousAuthenticationFilter,因此,如果没有早期的身份验证处理机制更新SecurityContextHolder,则将在此处放置一个匿名Authentication对象

  9. ExceptionTranslationFilter,以捕获任何Spring Security异常,以便可以返回HTTP错误响应或可以启动适当的AuthenticationEntryPoint

  10. 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元素中声明UsernamePasswordAuthenticationFilterJwtAuthenticationFilter,但这取决于每个过滤器的具体行为。两种方法都是可行的,最后选择哪种方法取决于自己的偏好。

配置两个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中对其进行配置,并相信它已被证明的行为,而不是从头开始。


3
这是很有启发性的解释。一个有关“您可能在同一个FilterchainProxy中配置多个身份验证处理机制”的问题。如果声明多个(Spring实现)身份验证过滤器,后者会覆盖由第一个身份验证执行的身份验证吗?这与拥有多个身份验证提供者有何关系?
Tuomas Toivonen

此外,文档指出SecurityContextPersistenceFilter负责清理SecurityContext,这对线程池很重要。如果我省略它或提供自定义实现,则必须手动执行清理,对吗?定制链时还有更多类似的陷阱吗?
Tuomas Toivonen

1
@TuomasToivonen在您最后的评论中的问题之后,我编辑了我的答案
jlumietu

1
@jlumietu在(“ / filterChain)旁边的Java注释中缺少引号。另外,您将该方法放在哪里?我试图将其添加到控制器中,并且有:No qualifying bean of type 'org.springframework.security.web.FilterChainProxy' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
Dimitri Kopriwa

@BigDong确保您已在web.xml或java webapp配置以及spring配置中声明了SpringSecurityFilterChain。就像您一样,此代码段必须包含在Controller中。是的,您对遗漏的报价很
满意

4

UsernamePasswordAuthenticationFilter仅用于/login,后面的过滤器没有?

不,UsernamePasswordAuthenticationFilterextends AbstractAuthenticationProcessingFilter,其中包含一个RequestMatcher,表示您可以定义自己的处理网址,此过滤器仅处理RequestMatcher与请求网址匹配的内容,默认处理网址为/login

如果UsernamePasswordAuthenticationFilter执行,以后的过滤器仍然可以处理请求chain.doFilter(request, response);

有关核心装配工的更多详细信息

表单登录名称空间元素是否自动配置这些过滤器?

UsernamePasswordAuthenticationFilter由创建<form-login>,这些是标准过滤器别名和排序

是否每个请求(无论是否经过身份验证)都到达非登录URL的FilterSecurityInterceptor?

这取决于先前的装配工是否成功,但FilterSecurityInterceptor通常是最后一个装配工。

配置两个http元素会创建两个springSecurityFitlerChains吗?

是的RequestMatcher,如果每个fitlerChain都有一个RequestMatcher与请求匹配,则请求将由fitler链中的fitler处理。

RequestMatcher如果您不配置模式,则默认值匹配所有请求,也可以配置特定的网址(<http pattern="/rest/**")。

如果您想了解有关钳工的更多信息,我认为您可以检查Spring Security中的源代码。 doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)


4

Spring Security是一个基于过滤器的框架,它在应用程序之前使用代理过滤器或Spring托管bean植入了WALL(HttpFireWall)。您的请求必须通过多个过滤器才能到达您的API。

Spring Security中的执行顺序

  1. WebAsyncManagerIntegrationFilter 提供SecurityContext和Spring Web的WebAsyncManager之间的集成。

  2. SecurityContextPersistenceFilter此过滤器将仅对每个请求执行一次,使用在请求之前从已配置的SecurityContextRepository获得的信息填充SecurityContextHolder,并在请求完成并清除上下文所有者后将其存储回存储库中。
    检查请求中的现有会话。如果有新请求,则将创建SecurityContext;否则,如果请求具有会话,则将从存储库中获取现有的安全上下文

  3. HeaderWriterFilter 过滤实现以将标头添加到当前响应。

  4. LogoutFilter如果请求URL是/logout(对于默认配置),或者如果请求URL符合项目RequestMatcher在被配置LogoutConfigurer然后

    • 清除安全上下文。
    • 使会话无效
    • 删除所有具有在其中配置的cookie名称的cookie LogoutConfigurer
    • 重定向到默认的注销成功URL /或配置的注销成功URL或调用配置的logoutSuccessHandler。
  5. UsernamePasswordAuthenticationFilter

    • 对于除loginProcessingUrl之外的任何请求URL,此过滤器将不会进一步处理,但过滤器链只会继续。
    • 如果请求的URL匹配(必须HTTP POST)默认/login或比赛.loginProcessingUrl()中的配置FormLoginConfigurer,然后UsernamePasswordAuthenticationFilter尝试进行身份验证。
    • 默认的登录表单参数是用户名和密码,可以被覆盖usernameParameter(String)passwordParameter(String)
    • 设置将.loginPage() 覆盖默认值
    • 尝试身份验证时
      • 一个Authentication对象(UsernamePasswordAuthenticationToken或任何实现Authentication您的自定义auth过滤器的)。
      • 并且authenticationManager.authenticate(authToken)将被调用
      • 请注意,我们可以配置任意数量的AuthenticationProvider身份验证方法,尝试所有身份验证提供程序并检查任何身份验证提供程序supportsauthToken / authentication对象,支持的身份验证提供程序将用于身份验证。并在成功认证的情况下返回Authentication对象,否则抛出AuthenticationException
    • 如果将创建并authenticationSuccessHandler调用身份验证成功会话,它将重定向到已配置的目标url(默认为/
    • 如果身份验证失败的用户变为未经身份验证的用户,则链继续。
  6. SecurityContextHolderAwareRequestFilter,如果您使用它来将可识别Spring Security的HttpServletRequestWrapper安装到servlet容器中

  7. AnonymousAuthenticationFilter检测SecurityContextHolder中是否没有身份验证对象,如果未找到身份验证对象,则使用授予的权限创建Authentication对象(AnonymousAuthenticationTokenROLE_ANONYMOUS。此处AnonymousAuthenticationToken有助于识别未经身份验证的用户的后续请求。

调试日志
DEBUG - /app/admin/app-config at position 9 of 12 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
DEBUG - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@aeef7b36: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS' 
  1. ExceptionTranslationFilter,以捕获任何Spring Security异常,以便可以返回HTTP错误响应或可以启动适当的AuthenticationEntryPoint

  2. FilterSecurityInterceptor
    会有FilterSecurityInterceptor哪些进来,从获得认证对象的过滤器链几乎是最后的SecurityContext和被授予的权限列表(角色授予),它会做出决定是否允许该请求到达请求的资源与否,决定由匹配与制造在中AntMatchers配置的允许HttpSecurityConfiguration

考虑例外401-未经授权和403-禁止。这些决定将在过滤器链的最后完成

  • 未经身份验证的用户尝试访问公共资源- 允许
  • 未经身份验证的用户尝试访问受保护的资源-401-未经授权
  • 经过身份验证的用户尝试访问受限资源(受限于其角色)-403-禁止访问

注:用户请求不仅在上述过滤器流,但也有其他的过滤器太这里没有显示(ConcurrentSessionFilterRequestCacheAwareFilterSessionManagementFilter...)
当您使用自定义的身份验证过滤器,而不是这将是不同的UsernamePasswordAuthenticationFilter
如果您配置JWT身份验证过滤器并省略,则情况会有所不同.formLogin() i.e, UsernamePasswordAuthenticationFilter它,则情况将完全不同。


仅供参考。spring-web和spring-security中的过滤器
注意:请参考pic中的包名称,因为orm和我的自定义实现的过滤器中还有其他过滤器。

在此处输入图片说明

从文档中过滤器的顺序为

  • ChannelProcessingFilter
  • ConcurrentSessionFilter
  • SecurityContextPersistenceFilter
  • 注销过滤器
  • X509AuthenticationFilter
  • AbstractPreAuthenticatedProcessingFilter
  • CasAuthenticationFilter
  • UsernamePasswordAuthenticationFilter
  • ConcurrentSessionFilter
  • OpenIDAuthenticationFilter
  • DefaultLoginPageGeneratingFilter
  • DefaultLogoutPageGeneratingFilter
  • ConcurrentSessionFilter
  • DigestAuthenticationFilter
  • BearerTokenAuthenticationFilter
  • BasicAuthenticationFilter
  • RequestCacheAwareFilter
  • SecurityContextHolderAwareRequestFilter
  • JaasApiIntegrationFilter
  • RememberMeAuthenticationFilter
  • 匿名身份验证过滤器
  • SessionManagementFilter
  • ExceptionTranslationFilter
  • FilterSecurityInterceptor
  • SwitchUserFilter

您还可以参考
最常见的方式来验证现代Web应用程序吗?
Spring Security上下文中的身份验证和授权之间的区别?

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.