Spring Security 过滤器链是如何工作的

新手上路,请多包涵

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

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

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

我很困惑如何使用这些过滤器。是不是对于spring提供的form-login, UsernamePasswordAuthenticationFilter 只用于 /login ,后面的filter没有? 表单登录 命名空间元素是否自动配置这些过滤器?每个请求(是否经过身份验证)是否都到达非登录 url 的 FilterSecurityInterceptor

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

配置两个 http 元素会创建两个 springSecurityFitlerChains 吗? UsernamePasswordAuthenticationFilter 是否默认关闭,直到我声明 form-login ? How do I replace SecurityContextPersistenceFilter with a filter which will obtain Authentication from existing JWT-token rather than JSESSIONID ?

原文由 Tuomas Toivonen 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 449
2 个回答

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

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

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

查看 当前稳定版本 4.2.1 文档,第 13.3 节过滤器排序,您可以看到整个过滤器链的过滤器组织:

13.3 过滤器排序

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

  1. ChannelProcessingFilter ,因为它可能需要重定向到不同的协议

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

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

  4. 身份验证处理机制 - UsernamePasswordAuthenticationFilterCasAuthenticationFilterBasicAuthenticationFilter 等 - 以便可以修改 SecurityContextHolder 以包含有效的身份验证请求令牌

  5. SecurityContextHolderAwareRequestFilter ,如果您使用它来将 Spring Security 感知 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 并在访问被拒绝时引发异常

现在,我将尝试逐一回答您的问题:

我很困惑如何使用这些过滤器。是不是spring提供的form-login,UsernamePasswordAuthenticationFilter只用于/login,后面的filter没有?表单登录命名空间元素是否自动配置这些过滤器?每个请求(是否经过身份验证)是否都到达非登录 url 的 FilterSecurityInterceptor?

配置 <security-http> 部分后,您必须至少为每个部分提供一种身份验证机制。这必须是与我刚刚引用的 Spring Security 文档的 13.3 过滤器排序部分中的第 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> 元素,所有默认过滤器都包含在内,但它们都不是身份验证类型(13.3 过滤器排序部分中的第 4 组)。所以这实际上意味着只需声明 security:http 元素,SecurityContextPersistenceFilter、ExceptionTranslationFilter 和 FilterSecurityInterceptor 就会自动配置。

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

如果我将基本的 <form-login> 添加到配置中,这样:

 <security:http authentication-manager-ref="mainAuthenticationManager">
    <security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
    <security:form-login />
</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.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 中创建和配置。

所以,现在,问题是:

是不是spring提供的form-login,UsernamePasswordAuthenticationFilter只用于/login,后面的filter没有?

是的,它用于在请求与 UsernamePasswordAuthenticationFilter url 匹配的情况下尝试完成登录处理机制。这个 url 可以配置甚至改变它的行为来匹配每个请求。

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

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

不,表单登录元素配置 UsernamePasswordAUthenticationFilter,如果您不提供登录页面 url,它还会配置 org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter,它以简单的自动生成登录结束页。

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

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

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

如果我想使用从登录中检索到的 JWT 令牌来保护我的 REST API 怎么办?我必须配置两个命名空间配置http标签,对吗?另一个用于 /login with UsernamePasswordAuthenticationFilter ,另一个用于 REST url,自定义 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 Header 中恢复,另一种是从自己的 rest 请求的请求体中恢复。事实上,如果一个 http 请求同时提供身份验证令牌作为 Http 标头和请求主体,两个过滤器都会尝试执行将其委托给管理器的身份验证机制,但可以很容易地避免简单地检查请求是否是已经在每个过滤器的 doFilter() 方法的开头进行了身份验证。

拥有多个身份验证过滤器与拥有多个身份验证提供程序有关,但不要强求。在我之前公开的案例中,我有两个身份验证过滤器,但我只有一个身份验证提供程序,因为这两个过滤器创建相同类型的身份验证对象,因此在这两种情况下,身份验证管理器都将其委托给同一个提供程序。

与此相反,我也有一种情况,我只发布一个 UsernamePasswordAuthenticationFilter 但用户凭据都可以包含在 DB 或 LDAP 中,所以我有两个 UsernamePasswordAuthenticationToken 支持提供者,并且 AuthenticationManager 将任何身份验证尝试从过滤器委托给提供者seucentially 验证凭据。

所以,我认为很明显,无论是身份验证过滤器的数量决定身份验证提供者的数量,还是提供者的数量决定过滤器的数量。

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

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

SecurityContextPersistenceFilterSecurityContextRepository 实现中委托对 SecurityContext 的搜索。默认情况下,使用 HttpSessionSecurityContextRepository ,但这可以使用过滤器的构造函数之一进行更改。因此,最好编写一个适合您需要的 SecurityContextRepository 并在 SecurityContextPersistenceFilter 中配置它,相信它已经证明的行为而不是从头开始制作。

原文由 jlumietu 发布,翻译遵循 CC BY-SA 3.0 许可协议

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

Spring Security 中的执行顺序

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

  2. SecurityContextPersistenceFilter 此过滤器每个请求只执行一次,使用请求之前从配置的 SecurityContextRepository 获得的信息填充 SecurityContextHolder,并在请求完成后将其存储回存储库并清除上下文持有者。

_检查现有会话的请求。如果有新请求,则将创建 SecurityContext 否则,如果请求具有会话,则将从 respository 获取现有的安全上下文_。

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

  2. LogoutFilter If request url is /logout (for default configuration) or if request url matches RequestMatcher configured in LogoutConfigurer then

  • 清除安全上下文。
  • 使会话无效
  • 删除所有cookie名称在 LogoutConfigurer 中配置的cookie
  • 重定向到默认注销成功 url / 或配置的注销成功 url 或调用配置的 logoutSuccessHandler。
  1. UsernamePasswordAuthenticationFilter
  • 对于除 loginProcessingUrl 之外的任何请求 url,此过滤器将不会进一步处理,但过滤器链会继续。

  • If requested URL is matches(must be HTTP POST ) default /login or matches .loginProcessingUrl() configured in FormLoginConfigurer then UsernamePasswordAuthenticationFilter 尝试身份验证。

  • 默认登录表单参数是用户名和密码,可以被 usernameParameter(String) , passwordParameter(String) 覆盖。

  • 设置 .loginPage() 覆盖默认值

  • 尝试身份验证时

    • 创建了一个 Authentication 对象( UsernamePasswordAuthenticationToken 或 --- Authentication --- 的任何实现,如果是自定义身份验证过滤器)。

    • authenticationManager.authenticate(authToken) 将被调用

    • 请注意,我们可以配置任意数量的 AuthenticationProvider 身份验证方法尝试所有身份验证提供程序并检查任何身份验证提供程序 supports authToken/身份验证对象,支持身份验证提供程序将用于身份验证。并在身份验证成功的情况下返回身份验证对象,否则抛出 AuthenticationException

  • 如果将创建身份验证成功会话并调用 authenticationSuccessHandler 将调用重定向到配置的目标 url(默认为 /

  • 如果身份验证失败,用户将成为未经身份验证的用户,并且链会继续。

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

  2. AnonymousAuthenticationFilter if there is no Authentication object in the SecurityContextHolder, if no authentication object found, creates Authentication object ( AnonymousAuthenticationToken ) with granted authority 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'

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

  2. FilterSecurityInterceptor

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

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

  • 未经身份验证的用户尝试访问公共资源 - 允许

  • 未经身份验证的用户试图访问受保护的资源 - 401-UnAuthorized

  • 经过身份验证的用户试图访问受限资源(因其角色而受限)- 403-Forbidden


注意:用户请求不仅在上述过滤器中流动,还有其他过滤器也未在此处显示。( ConcurrentSessionFilterRequestCacheAwareFilterSessionManagementFilter …)0

当您使用自定义身份验证过滤器而不是 UsernamePasswordAuthenticationFilter 时,情况会有所不同。

如果您配置 JWT 身份验证过滤器并省略 .formLogin() i.e, UsernamePasswordAuthenticationFilter 它将变得完全不同。


仅供参考。 spring-web 和 spring-security 中的过滤器

注意:**参考图片中的包名称**,因为还有一些来自 orm 的其他过滤器和我自定义实现的过滤器。

在此处输入图像描述

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

  • 通道处理过滤器
  • 并发会话过滤器
  • SecurityContextPersistenceFilter
  • 注销过滤器
  • X509AuthenticationFilter
  • AbstractPreAuthenticatedProcessingFilter
  • CasAuthenticationFilter
  • 用户名密码AuthenticationFilter
  • 并发会话过滤器
  • OpenIDAuthenticationFilter
  • 默认登录页面生成过滤器
  • DefaultLogoutPageGeneratingFilter
  • 并发会话过滤器
  • 摘要认证过滤器
  • BearerTokenAuthenticationFilter
  • 基本认证过滤器
  • 请求缓存感知过滤器
  • SecurityContextHolderAwareRequestFilter
  • JaasApiIntegrationFilter
  • RememberMeAuthenticationFilter
  • 匿名身份验证过滤器
  • 会话管理过滤器
  • 异常翻译过滤器
  • 过滤器安全拦截器
  • 切换用户过滤器

你也可以参考

验证现代 Web 应用程序的最常用方法?

Spring Security上下文中的身份验证和授权之间的区别?

原文由 PraveenKumar Lalasangi 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题