Spring Security Filter Chain的工作原理

时间:2017-01-05 08:14:11

标签: spring authentication spring-security filter jwt

我意识到Spring安全构建在过滤器链上,它将拦截请求,检测(缺少)身份验证,重定向到身份验证入口点或将请求传递给授权服务,并最终让请求命中servlet或抛出安全异常(未经身份验证或未经授权)。 DelegatingFitlerProxy 将这些过滤器粘合在一起。为了执行他们的任务,这些过滤器访问服务,例如 UserDetailsS​​ervice AuthenticationManager

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

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

我很困惑如何使用这些过滤器。对于spring提供的form-login, UsernamePasswordAuthenticationFilter 仅用于 / login ,而后者的过滤器不是? form-login 命名空间元素是否自动配置这些过滤器?是否每个请求(经过身份验证或未经过身份验证)都会到达 FilterSecurityInterceptor 以获取非登录网址?

如果我想使用从登录检索的 JWT-token 来保护我的REST API怎么办?我必须配置两个命名空间配置http标记,权利?其他一个用于带有UsernamePasswordAuthenticationFilter / login ,另一个带有自定义JwtAuthenticationFilter的REST网址。

配置两个http元素会创建两个springSecurityFitlerChains吗?默认情况下UsernamePasswordAuthenticationFilter是否已关闭,直到我声明form-login为止?如何将SecurityContextPersistenceFilter替换为Authentication,从现有JWT-token而不是JSESSIONID获取{{1}}?

3 个答案:

答案 0 :(得分:139)

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

  

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

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

查看current stable release 4.2.1 documentation部分13.3 Filter Ordering,您可以看到整个过滤器链的过滤器组织:

  

13.3过滤器订购

     

过滤器在链中定义的顺序非常重要。   无论您实际使用哪种过滤器,订单都应该如此   如下:

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

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

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

  6.   
  7. 身份验证处理机制 -    UsernamePasswordAuthenticationFilter CasAuthenticationFilter ,    BasicAuthenticationFilter 等 - 以便SecurityContextHolder可以   被修改为包含有效的身份验证请求令牌

  8.   
  9. SecurityContextHolderAwareRequestFilter ,如果您使用它来安装Spring Security感知的HttpServletRequestWrapper到您的   servlet容器

  10.   
  11. JaasApiIntegrationFilter ,如果 JaasAuthenticationToken 位于SecurityContextHolder中,则会将FilterChain作为   JaasAuthenticationToken中的主题

  12.   
  13. RememberMeAuthenticationFilter ,这样如果没有早期的身份验证处理机制更新了SecurityContextHolder,   并且该请求提供了一个cookie,可以记住我的服务   发生,将放置一个合适的记住身份验证对象   有

  14.   
  15. AnonymousAuthenticationFilter ,这样如果没有早期的身份验证处理机制更新了SecurityContextHolder,   匿名身份验证对象将放在那里

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

  18.   
  19. FilterSecurityInterceptor ,用于在访问被拒绝时保护Web URI并引发异常

  20.   

现在,我将逐一尝试继续你的问题:

  

我对如何使用这些过滤器感到困惑。是春天吗?   提供表单登录,仅使用UsernamePasswordAuthenticationFilter   for / login,后面的过滤器不是?是否为form-login命名空间   元素自动配置这些过滤器?是每个请求   (已验证或未验证)到达非登录的FilterSecurityInterceptor   URL?

配置<security-http>部分后,对于每个部分,您必须至少提供一种身份验证机制。这必须是我刚刚引用的Spring Security文档的13.3过滤器订购部分中与第4组匹配的过滤器之一。

这是最低有效安全性: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来获取它们,@ Restow的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>元素,包含所有默认过滤器,但它们都不是身份验证类型(13.3过滤器排序部分中的第4组)。所以它实际上意味着只需声明security:http元素,SecurityContextPersistenceFilter,ExceptionTranslationFilter和FilterSecurityInterceptor就会自动配置。

实际上,应该配置一个身份验证处理机制,甚至安全命名空间bean处理它的声明,在启动期间抛出错误,但可以绕过在<http:security> <中添加entry-point-ref属性/ p>

如果我在配置中添加基本<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"
    }

现在,在FilterChainProxy中创建并配置了这两个过滤器org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter和org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter。

所以,现在,问题:

  

对于春天提供的表格登录,   UsernamePasswordAuthenticationFilter仅用于/ login,和   后面的过滤器不是吗?

是的,它用于尝试完成登录处理机制,以防请求与UsernamePasswordAuthenticationFilter url匹配。此网址可以配置甚至更改其行为以匹配每个请求。

您也可以在同一FilterchainProxy中配置多个身份验证处理机制(例如HttpBasic,CAS等)。

  

form-login命名空间元素是否自动配置这些过滤器?

不,form-login元素配置UsernamePasswordAUthenticationFilter,如果你没有提供登录页面网址,它还会配置org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter,结束在一个简单的自动生成的登录页面中。

默认情况下,只需创建一个没有<security:http>属性的security:"none"元素,即可自动配置其他过滤器。

  

是否每个请求(经过身份验证或未经过身份验证)都会到达非登录网址的FilterSecurityInterceptor?

每个请求都应该到达它,因为它是负责请求是否有权访问请求的URL的元素。但之前处理的一些过滤器可能会停止过滤器链处理,而不是调用FilterChain.doFilter(request, response);。例如,如果请求没有csrf参数,CSRF过滤器可能会停止过滤器链处理。

  

如果我想使用从登录检索的JWT令牌来保护我的REST API怎么办?我必须配置两个命名空间配置http标签,权限?其他一个用于/ {登录UsernamePasswordAuthenticationFilter,另一个用于REST网址,用自定义JwtAuthenticationFilter

不,你不是被迫这样做的。您可以在同一个http元素中声明UsernamePasswordAuthenticationFilterJwtAuthenticationFilter,但这取决于每个过滤器的具体行为。这两种方法都是可能的,哪种方法最终取决于自己的偏好。

  

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

是的,那是真的

  

默认情况下,UsernamePasswordAuthenticationFilter是否已关闭,直到我声明表单登录?

是的,你可以在我发布的每个配置中提出的过滤器中看到它

  

如何将SecurityContextPersistenceFilter替换为一个,它将从现有的JWT-token而不是JSESSIONID获取身份验证?

您可以避免使用SecurityContextPersistenceFilter,只需在<http:element>中配置session strategy即可。只需像这样配置:

<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" />

编辑:

  

关于&#34的一个问题;您也可以在同一个FilterchainProxy&#34;中配置多个身份验证处理机制。如果声明多个(Spring实现)身份验证过滤器,后者是否会覆盖第一个执行的身份验证?这与拥有多个身份验证提供程序有何关系?

这最终取决于每个过滤器本身的实现,但事实上,后者的认证过滤器至少能够覆盖最终由前面的过滤器进行的任何先前认证。

但这不可能发生。我在安全REST服务中有一些生产案例,我使用一种授权令牌,可以作为Http头或在请求体内提供。所以我配置了两个过滤器来恢复该令牌,在一种情况下来自Http Header,另一个来自自己的rest请求的请求体。确实如此,如果一个http请求将该身份验证令牌同时提供为Http头和请求体内,则两个过滤器都会尝试执行将其委派给管理器的身份验证机制,但只需检查就可以轻松避免如果请求已经在每个过滤器的doFilter()方法开始时已经过身份验证。

拥有多个身份验证过滤器与拥有多个身份验证提供程序有关,但请勿强制使用。在我之前暴露的情况下,我有两个身份验证过滤器,但我只有一个身份验证提供程序,因为两个过滤器都创建了相同类型的身份验证对象,所以在这两种情况下,身份验证管理器都将它委托给同一个提供程序。

与此相反,我也有一个场景,我只发布一个UsernamePasswordAuthenticationFilter,但用户凭据都可以包含在DB或LDAP中,因此我有两个UsernamePasswordAuthenticationToken支持提供程序,并且AuthenticationManager委派来自过滤器的任何身份验证尝试以供应商的方式来验证凭证。

因此,我认为很明显,认证过滤器的数量既不会决定认证提供商的数量,也不会决定提供商确定过滤器数量的数量。

  

此外,文档说明SecurityContextPersistenceFilter负责清理SecurityContext,这对于线程池很重要。如果我省略它或提供自定义实现,我必须手动执行清理,对吧?在定制链条时是否有更多相似的问题?

之前我没有仔细查看过滤器,但是在您上一个问题之后我已经检查了它的实现,并且通常在Spring中,几乎所有内容都可以配置,扩展或覆盖。

SecurityContextPersistenceFilter代表在SecurityContextRepository实现中搜索SecurityContext。默认情况下,使用HttpSessionSecurityContextRepository,但可以使用过滤器的一个构造函数更改此值。因此,编写一个符合您需求的SecurityContextRepository可能会更好,只需在SecurityContextPersistenceFilter中配置它,相信它已被证实的行为,而不是从头开始全部制作。

答案 1 :(得分:4)

  

UsernamePasswordAuthenticationFilter仅用于/login,而后一种过滤器不用于?

不,UsernamePasswordAuthenticationFilter扩展AbstractAuthenticationProcessingFilter,这包含RequestMatcher,这意味着您可以定义自己的处理网址,此过滤器仅处理RequestMatcher匹配请求url,默认处理网址为/login

如果UsernamePasswordAuthenticationFilter执行chain.doFilter(request, response);

,后续过滤器仍然可以处理请求

有关core fitlers

的更多详情
  

form-login命名空间元素是否自动配置这些过滤器?

UsernamePasswordAuthenticationFilter<form-login>创建,这些是Standard Filter Aliases and Ordering

  

是否每个请求(经过身份验证或未经过身份验证)都会到达非登录网址的FilterSecurityInterceptor?

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

  

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

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

如果您未配置模式,默认RequestMatcher会匹配所有请求,或者您可以配置特定网址(<http pattern="/rest/**")。

如果你想了解更多有关fitlers的信息,我想你可以查看spring security中的源代码。 doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)

答案 2 :(得分:3)

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

Spring Security中的执行顺序

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

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

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

  4. LogoutFilter 如果请求url为/logout(用于默认配置),或者如果在RequestMatcher中配置了请求url数学LogoutConfigurer,则

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

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

  7. AnonymousAuthenticationFilter检测SecurityContextHolder中是否没有身份验证对象,如果找不到身份验证对象,则创建具有授予权限Authentication的{​​{1}}对象(AnonymousAuthenticationToken) 。这里的ROLE_ANONYMOUS有助于识别未经身份验证的用户的后续请求。

调试日志
AnonymousAuthenticationToken
  1. 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' ,以捕获任何Spring Security异常,以便可以返回HTTP错误响应或可以启动适当的AuthenticationEntryPoint

  2. ExceptionTranslationFilter
    将有FilterSecurityInterceptor几乎在过滤器链中排在最后,该过滤器链从FilterSecurityInterceptor获取身份验证对象,并获得授权列表(已授予角色),并且它将决定是否允许此请求到达所请求的是否提供资源,通过与在SecurityContext中配置的允许的AntMatchers进行匹配来做出决定。

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

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

注意:“用户请求”不仅会在上述过滤器中流动,而且这里也未显示其他过滤器。(HttpSecurityConfigurationConcurrentSessionFilterRequestCacheAwareFilter ...)
使用自定义身份验证过滤器而不是SessionManagementFilter时会有所不同。
如果您配置JWT身份验证过滤器并省略UsernamePasswordAuthenticationFilter,则情况将有所不同。


仅供参考。 spring-web和spring-security中的过滤器
注意:在图片中引用软件包名称,因为orm和我的自定义实现的过滤器中还有其他过滤器。

enter image description here

  

From Documentation过滤器的排序方式为

     
      
  • ChannelProcessingFilter
  •   
  • ConcurrentSessionFilter
  •   
  • SecurityContextPersistenceFilter
  •   
  • LogoutFilter
  •   
  • X509AuthenticationFilter
  •   
  • AbstractPreAuthenticatedProcessingFilter
  •   
  • CasAuthenticationFilter
  •   
  • UsernamePasswordAuthenticationFilter
  •   
  • ConcurrentSessionFilter
  •   
  • OpenIDAuthenticationFilter
  •   
  • DefaultLoginPageGeneratingFilter
  •   
  • DefaultLogoutPageGeneratingFilter
  •   
  • ConcurrentSessionFilter
  •   
  • DigestAuthenticationFilter
  •   
  • BearerTokenAuthenticationFilter
  •   
  • BasicAuthenticationFilter
  •   
  • RequestCacheAwareFilter
  •   
  • SecurityContextHolderAwareRequestFilter
  •   
  • JaasApiIntegrationFilter
  •   
  • RememberMeAuthenticationFilter
  •   
  • AnonymousAuthenticationFilter
  •   
  • SessionManagementFilter
  •   
  • ExceptionTranslationFilter
  •   
  • FilterSecurityInterceptor
  •   
  • SwitchUserFilter
  •   

您也可以参考
most common way to authenticate a modern web app?
difference between authentication and authorization in context of Spring Security?