Spring Security Filter Chain的工作原理

Tuo*_*nen 113 authentication spring filter spring-security jwt

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

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

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

我很困惑如何使用这些过滤器.对于弹簧提供的form-login,UsernamePasswordAuthenticationFilter仅用于/ login,而后者的过滤器不是?form-login名称空间元素是否自动配置这些过滤器?是否每个请求(已验证或未验证)都会到达非登录URL的FilterSecurityInterceptor?

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

配置两个http元素会创建两个springSecurityFitlerChains吗?是UsernamePasswordAuthenticationFilter默认是关闭的,直到我宣布form-login?如何更换SecurityContextPersistenceFilter一个,Authentication从现有JWT-token而不是JSESSIONID?

jlu*_*etu 161

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,如果你正在使用它的春季安全意识到了HttpServletRequestWrapper安装到你的servlet容器

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

  7. RememberMeAuthenticationFilter,这样如果没有早期的身份验证处理机制更新了SecurityContextHolder,并且请求提供了一个启用记住我服务的cookie,那么一个合适的记忆身份验证对象将放在那里

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

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

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

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

我很困惑如何使用这些过滤器.对于弹簧提供的form-login,UsernamePasswordAuthenticationFilter仅用于/ login,而后者的过滤器不是?form-login名称空间元素是否自动配置这些过滤器?是否每个请求(已验证或未验证)都会到达非登录URL的FilterSecurityInterceptor?

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

这是最低有效安全性:http元素可以配置:

<security:http authentication-manager-ref="mainAuthenticationManager" 
               entry-point-ref="serviceAccessDeniedHandler">
    <security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
</security:http>
Run Code Online (Sandbox Code Playgroud)

只是这样做,这些过滤器在过滤器链代理中配置:

{
        "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"
    }
Run Code Online (Sandbox Code Playgroud)

注意:我通过创建一个简单的RestController来获取它们,这些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;
    }
Run Code Online (Sandbox Code Playgroud)

在这里我们可以看到,仅通过声明<security:http>具有一个最小配置的元素,包括所有默认过滤器,但它们都不是认证类型(13.3过滤器排序部分中的第4组).所以它实际上意味着仅通过声明security:http元素,SecurityContextPersistenceFilter,ExceptionTranslationFilter和FilterSecurityInterceptor是自动配置的.

实际上,应该配置一个身份验证处理机制,甚至安全命名空间bean处理它的声明,在启动期间抛出错误,但可以绕过添加一个入口点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>
Run Code Online (Sandbox Code Playgroud)

现在,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"
    }
Run Code Online (Sandbox Code Playgroud)

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

那么,现在,问题:

对于弹簧提供的form-login,UsernamePasswordAuthenticationFilter仅用于/ login,而后者的过滤器不是?

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

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

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

不,form-login元素配置UsernamePasswordAUthenticationFilter,如果你没有提供登录页面url,它还会配置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 with UsernamePasswordAuthenticationFilter,另一个用于REST url,带有自定义JwtAuthenticationFilter.

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

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

是的,这是真的

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

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

如何将SecurityContextPersistenceFilter替换为一个,它将从现有的JWT-token而不是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" />
Run Code Online (Sandbox Code Playgroud)

编辑:

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

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

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

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

与此相反,我也有一个场景,我只发布一个UsernamePasswordAuthenticationFilter,但用户凭据都可以包含在DB或LDAP中,因此我有两个UsernamePasswordAuthenticationToken支持提供程序,AuthenticationManager委托从过滤器到提供程序的任何身份验证尝试有意识地验证凭证.

因此,我认为很明显,身份验证过滤器的数量既不会确定身份验证提供程序的数量,也不会决定提供程序的数量来确定过滤器的数量.

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

我之前没有仔细研究过这个过滤器,但是在你上一个问题之后我一直在检查它的实现,并且通常在Spring中,几乎所有东西都可以配置,扩展或覆盖.

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

  • 这是启发性的解释.关于"您可以在同一FilterchainProxy中配置多个身份验证处理机制"的一个问题.如果声明多个(Spring实现)身份验证过滤器,后者是否会覆盖第一个执行的身份验证?这与多个身份验证提供程序有何关系? (3认同)

Pra*_*ngi 19

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

Spring Security 中的执行顺序

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

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

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

  4. LogoutFilter如果请求 url 是/logout(对于默认配置)或者如果请求 url 匹配RequestMatcher配置,LogoutConfigurer则

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

  2. AnonymousAuthenticationFilter检测 SecurityContextHolder 中是否没有 Authentication 对象,如果未找到身份验证对象,则创建具有授予权限的Authentication对象 ( AnonymousAuthenticationToken) ROLE_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' 
Run Code Online (Sandbox Code Playgroud)
  1. ExceptionTranslationFilter, 捕获任何 Spring Security 异常,以便可以返回 HTTP 错误响应或可以启动适当的 AuthenticationEntryPoint

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

考虑例外 401-UnAuthorized 和 403-Forbidden。这些决定将在过滤器链的最后完成

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

注意:用户请求不仅在上述过滤器中流动,还有其他过滤器也未在此处显示。( ConcurrentSessionFilter, RequestCacheAwareFilter, SessionManagementFilter...)
当您使用自定义身份验证过滤器而不是UsernamePasswordAuthenticationFilter.
如果您配置 JWT auth 过滤器并省略.formLogin() i.e, UsernamePasswordAuthenticationFilter它,情况会有所不同,它将成为完全不同的情况。


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

在此处输入图片说明

从文档中,过滤器的顺序为

  • 通道处理过滤器
  • 并发会话过滤器
  • 安全上下文持久性过滤器
  • 注销过滤器
  • X509认证过滤器
  • AbstractPreAuthenticatedProcessingFilter
  • CasAuthenticationFilter
  • 用户名密码验证过滤器
  • 并发会话过滤器
  • OpenID认证过滤器
  • DefaultLoginPageGeneratingFilter
  • DefaultLogoutPageGeneratingFilter
  • 并发会话过滤器
  • 摘要认证过滤器
  • BearerTokenAuthenticationFilter
  • 基本认证过滤器
  • 请求缓存感知过滤器
  • SecurityContextHolderAwareRequestFilter
  • JaasApi集成过滤器
  • 记住我身份验证过滤器
  • 匿名身份验证过滤器
  • 会话管理过滤器
  • 异常翻译过滤器
  • 过滤安全拦截器
  • 切换用户过滤器

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


cha*_*luo 5

UsernamePasswordAuthenticationFilter只用于/login,后面的过滤器不是?

不,UsernamePasswordAuthenticationFilter是 extends AbstractAuthenticationProcessingFilter,其中包含一个RequestMatcher,意味着你可以定义自己的处理 url,这个过滤器只处理RequestMatcher匹配请求的 url,默认处理 url 是/login。

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

有关核心安装程序的更多详细信息

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

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

每个请求(无论是否经过身份验证)是否都会到达 FilterSecurityInterceptor 以获取非登录 url?

取决于前面的fitler是否成功,但FilterSecurityInterceptor一般是最后一个fitler。

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

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

RequestMatcher如果不配置pattern,则默认匹配所有请求,也可以配置具体的url ( <http pattern="/rest/**")。

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