上一文我们使用 Spring Security 实现了各种登录聚合的场面。其中我们是通过在 UsernamePasswordAuthenticationFilter
之前一个自定义的过滤器实现的。我怎么知道自定义过滤器要加在 UsernamePasswordAuthenticationFilter
之前。我在这个系列开篇说了 Spring Security 权限控制的一个核心关键就是 过滤器链 ,这些过滤器如下图进行过滤传递,甚至比这个更复杂!这只是一个最小单元。
Spring Security内置了一些过滤器,他们各有各的本事。如果你掌握了这些过滤器,很多实际开发中的需求和问题都很容易解决。今天我们来见识一下这些内置的过滤器。
在 Spring Security 初始化核心过滤器时 HttpSecurity
会通过将 Spring Security 内置的一些过滤器以 FilterComparator
提供的规则进行比较按照比较结果进行排序注册。
FilterComparator
维护了一个顺序的注册表 filterToOrder
。
FilterComparator() { Step order = new Step(INITIAL_ORDER, ORDER_STEP); put(ChannelProcessingFilter.class, order.next()); put(ConcurrentSessionFilter.class, order.next()); put(WebAsyncManagerIntegrationFilter.class, order.next()); put(SecurityContextPersistenceFilter.class, order.next()); put(HeaderWriterFilter.class, order.next()); put(CorsFilter.class, order.next()); put(CsrfFilter.class, order.next()); put(LogoutFilter.class, order.next()); filterToOrder.put( "org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter", order.next()); filterToOrder.put( "org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationRequestFilter", order.next()); put(X509AuthenticationFilter.class, order.next()); put(AbstractPreAuthenticatedProcessingFilter.class, order.next()); filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter", order.next()); filterToOrder.put( "org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter", order.next()); filterToOrder.put( "org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter", order.next()); put(UsernamePasswordAuthenticationFilter.class, order.next()); put(ConcurrentSessionFilter.class, order.next()); filterToOrder.put( "org.springframework.security.openid.OpenIDAuthenticationFilter", order.next()); put(DefaultLoginPageGeneratingFilter.class, order.next()); put(DefaultLogoutPageGeneratingFilter.class, order.next()); put(ConcurrentSessionFilter.class, order.next()); put(DigestAuthenticationFilter.class, order.next()); filterToOrder.put( "org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter", order.next()); put(BasicAuthenticationFilter.class, order.next()); put(RequestCacheAwareFilter.class, order.next()); put(SecurityContextHolderAwareRequestFilter.class, order.next()); put(JaasApiIntegrationFilter.class, order.next()); put(RememberMeAuthenticationFilter.class, order.next()); put(AnonymousAuthenticationFilter.class, order.next()); filterToOrder.put( "org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter", order.next()); put(SessionManagementFilter.class, order.next()); put(ExceptionTranslationFilter.class, order.next()); put(FilterSecurityInterceptor.class, order.next()); put(SwitchUserFilter.class, order.next()); }
这些就是所有内置的过滤器。 他们是通过下面的方法获取自己的序号:
private Integer getOrder(Class<?> clazz) { while (clazz != null) { Integer result = filterToOrder.get(clazz.getName()); if (result != null) { return result; } clazz = clazz.getSuperclass(); } return null; }
通过过滤器的类全限定名从注册表 filterToOrder
中获取自己的序号,如果没有直接获取到序号通过递归获取父类在注册表中的序号作为自己的序号,序号越小优先级越高。上面的过滤器并非全部会被初始化。有的需要额外引入一些功能包,有的看 HttpSecurity
的配置情况。 在上一篇 文章 中。我们禁用了 CSRF
功能,就意味着 CsrfFilter
不会被注册。
接下来我们就对这些内置过滤器进行一个系统的认识。 我们将按照默认顺序进行讲解 。
ChannelProcessingFilter
通常是用来过滤哪些请求必须用 https
协议, 哪些请求必须用 http
协议, 哪些请求随便用哪个协议都行。它主要有两个属性:
ChannelDecisionManager
用来判断请求是否符合既定的协议规则。它维护了一个 ChannelProcessor
列表 这些 ChannelProcessor
是具体用来执行 ANY_CHANNEL
策略 (任何通道都可以), REQUIRES_SECURE_CHANNEL
策略 (只能通过 https
通道), REQUIRES_INSECURE_CHANNEL
策略 (只能通过 http
通道)。 FilterInvocationSecurityMetadataSource
用来存储 url 与 对应的 ANY_CHANNEL
、 REQUIRES_SECURE_CHANNEL
、 REQUIRES_INSECURE_CHANNEL
的映射关系。 ChannelProcessingFilter
通过 HttpScurity#requiresChannel()
等相关方法引入其配置对象 ChannelSecurityConfigurer
来进行配置。
ConcurrentSessionFilter
主要用来判断 session
是否过期以及更新最新的访问时间。其流程为:
session
检测,如果不存在直接放行去执行下一个过滤器。存在则进行下一步。 sessionid
从 SessionRegistry
中获取 SessionInformation
,从 SessionInformation
中获取 session
是否过期;没有过期则更新 SessionInformation
中的访问日期; doLogout()
方法,这个方法会将 session
无效,并将 SecurityContext
中的 Authentication
中的权限置空,同时在 SecurityContenxtHoloder
中清除 SecurityContext
然后查看是否有跳转的 expiredUrl
,如果有就跳转,没有就输出提示信息。 ConcurrentSessionFilter
通过 SessionManagementConfigurer
来进行配置。
WebAsyncManagerIntegrationFilter
用于集成SecurityContext到Spring异步执行机制中的WebAsyncManager。用来处理异步请求的安全上下文。具体逻辑为:
WebAsyncManager
,如果尚未绑定,先做绑定。 asyncManager
中获取 key
为 CALLABLE_INTERCEPTOR_KEY
的安全上下文多线程处理器 SecurityContextCallableProcessingInterceptor
, 如果获取到的为 null
, SecurityContextCallableProcessingInterceptor
并绑定 CALLABLE_INTERCEPTOR_KEY
注册到 asyncManager
中。 这里简单说一下 SecurityContextCallableProcessingInterceptor
。它实现了接口 CallableProcessingInterceptor
,
当它被应用于一次异步执行时, beforeConcurrentHandling()
方法会在调用者线程执行,该方法会相应地从当前线程获取 SecurityContext
,然后被调用者线程中执行逻辑时,会使用这个 SecurityContext
,从而实现安全上下文从调用者线程到被调用者线程的传输。
WebAsyncManagerIntegrationFilter
通过 WebSecurityConfigurerAdapter#getHttp()
方法添加到 HttpSecurity
中成为 DefaultSecurityFilterChain
的一个链节。
SecurityContextPersistenceFilter
主要控制 SecurityContext
的在一次请求中的生命周期 。 请求来临时,创建 SecurityContext
安全上下文信息,请求结束时清空 SecurityContextHolder
。
SecurityContextPersistenceFilter
通过 HttpScurity#securityContext()
及相关方法引入其配置对象 SecurityContextConfigurer
来进行配置。
HeaderWriterFilter
用来给 http
响应添加一些 Header
,比如 X-Frame-Options
, X-XSS-Protection
, X-Content-Type-Options
。
你可以通过 HttpScurity#headers()
来定制请求 Header
。
跨域相关的过滤器。这是 Spring MVC Java
配置和 XML
命名空间 CORS
配置的替代方法, 仅对依赖于 spring-web
的应用程序有用(不适用于 spring-webmvc
)或 要求在 javax.servlet.Filter
级别进行CORS检查的安全约束链接。这个是目前官方的一些解读,但是我还是不太清楚实际机制。
你可以通过 HttpSecurity#cors()
来定制。
CsrfFilter
用于防止 csrf
攻击,前后端使用json交互需要注意的一个问题。
你可以通过 HttpSecurity.csrf()
来开启或者关闭它。在你使用 jwt
等 token
技术时,是不需要这个的。
LogoutFilter
很明显这是处理注销的过滤器。
你可以通过 HttpSecurity.logout()
来定制注销逻辑,非常有用。
和上面的有所不同,这个需要依赖 spring-scurity-oauth2
相关的模块。该过滤器是处理 OAuth2
请求首选重定向相关逻辑的。以后会我会带你们认识它,请多多关注公众号: Felordcn
。
这个需要用到 Spring Security SAML
模块,这是一个基于 SMAL
的 SSO
单点登录请求认证过滤器。
SAML
即安全断言标记语言,英文全称是 Security Assertion Markup Language
。它是一个基于 XML
的标准,用于在不同的安全域( security domain
)之间交换认证和授权数据。在 SAML
标准定义了身份提供者 ( identity provider
) 和服务提供者 ( service provider
),这两者构成了前面所说的不同的安全域。 SAML
是 OASIS
组织安全服务技术委员会( Security Services Technical Committee ) 的产品。
SAML
( Security Assertion Markup Language )是一个 XML
框架,也就是一组协议,可以用来传输安全声明。比如,两台远程机器之间要通讯,为了保证安全,我们可以采用加密等措施,也可以采用 SAML
来传输,传输的数据以 XML
形式,符合 SAML
规范,这样我们就可以不要求两台机器采用什么样的系统,只要求能理解 SAML
规范即可,显然比传统的方式更好。 SAML
规范是一组 Schema
定义。
可以这么说,在 Web Service
领域, schema
就是规范,在 Java
领域, API
就是规范
X509
认证过滤器。你可以通过 HttpSecurity#X509()
来启用和配置相关功能。
AbstractPreAuthenticatedProcessingFilter
处理处理经过预先认证的身份验证请求的过滤器的基类,其中认证主体已经由外部系统进行了身份验证。 目的只是从传入请求中提取主体上的必要信息,而不是对它们进行身份验证。
你可以继承该类进行具体实现并通过 HttpSecurity#addFilter
方法来添加个性化的 AbstractPreAuthenticatedProcessingFilter
。
CAS
单点登录认证过滤器 。依赖 Spring Security CAS 模块
这个需要依赖 spring-scurity-oauth2
相关的模块。 OAuth2
登录认证过滤器。处理通过 OAuth2
进行认证登录的逻辑。
这个需要用到 Spring Security SAML
模块,这是一个基于 SMAL
的 SSO
单点登录认证过滤器。
这个看过我相关文章的应该不陌生了。处理用户以及密码认证的核心过滤器。认证请求提交的 username
和 password
,被封装成 token
进行一系列的认证,便是主要通过这个过滤器完成的,在表单认证的方法中,这是最最关键的过滤器。
你可以通过 HttpSecurity#formLogin()
及相关方法引入其配置对象 FormLoginConfigurer
来进行配置。 我们在 Spring Security 实战干货: 玩转自定义登录 已经对其进行过个性化的配置和魔改。
参见3.2 ConcurrentSessionFilter。 该过滤器可能会被多次执行。
基于 OpenID
认证协议的认证过滤器。 你需要在依赖中依赖额外的相关模块才能启用它。
生成默认的登录页。默认 /login
。
生成默认的退出页。 默认 /logout
。
参见3.2 ConcurrentSessionFilter。 该过滤器可能会被多次执行。
Digest
身份验证是 Web
应用程序中流行的可选的身份验证机制 。 DigestAuthenticationFilter
能够处理 HTTP
头中显示的摘要式身份验证凭据。你可以通过 HttpSecurity#addFilter()
来启用和配置相关功能。
和 Digest
身份验证一样都是 Web
应用程序中流行的可选的身份验证机制 。 BasicAuthenticationFilter
负责处理 HTTP
头中显示的基本身份验证凭据。这个 Spring Security 的 Spring Boot 自动配置默认是启用的 。
BasicAuthenticationFilter
通过 HttpSecurity#httpBasic()
及相关方法引入其配置对象 HttpBasicConfigurer
来进行配置。
用于用户认证成功后,重新恢复因为登录被打断的请求。当匿名访问一个需要授权的资源时。会跳转到认证处理逻辑,此时请求被缓存。在认证逻辑处理完毕后,从缓存中获取最开始的资源请求进行再次请求。
RequestCacheAwareFilter
通过 HttpScurity#requestCache()
及相关方法引入其配置对象 RequestCacheConfigurer
来进行配置。
用来 实现 j2ee
中 Servlet Api
一些接口方法, 比如 getRemoteUser
方法、 isUserInRole
方法,在使用 Spring Security 时其实就是通过这个过滤器来实现的。
SecurityContextHolderAwareRequestFilter
通过 HttpSecurity.servletApi()
及相关方法引入其配置对象 ServletApiConfigurer
来进行配置。
适用于 JAAS
( Java
认证授权服务)。 如果 SecurityContextHolder
中拥有的 Authentication
是一个 JaasAuthenticationToken
,那么该 JaasApiIntegrationFilter
将使用包含在 JaasAuthenticationToken
中的 Subject
继续执行 FilterChain
。
处理 记住我
功能的过滤器。
RememberMeAuthenticationFilter
通过 HttpSecurity.rememberMe()
及相关方法引入其配置对象 RememberMeConfigurer
来进行配置。
匿名认证过滤器。 对于 Spring Security
来说,所有对资源的访问都是有 Authentication
的。对于无需登录( UsernamePasswordAuthenticationFilter
)直接可以访问的资源,会授予其匿名用户身份 。
AnonymousAuthenticationFilter
通过 HttpSecurity.anonymous()
及相关方法引入其配置对象 AnonymousConfigurer
来进行配置。
Session
管理器过滤器,内部维护了一个 SessionAuthenticationStrategy
用于管理 Session
。
SessionManagementFilter
通过 HttpScurity#sessionManagement()
及相关方法引入其配置对象 SessionManagementConfigurer
来进行配置。
主要来传输异常事件,还记得之前我们见过的 DefaultAuthenticationEventPublisher
吗?
这个过滤器决定了访问特定路径应该具备的权限,访问的用户的角色,权限是什么?访问的路径需要什么样的角色和权限?这些判断和处理都是由该类进行的。 如果你要实现动态权限控制就必须研究该类 。
SwitchUserFilter
是用来做账户切换的。默认的切换账号的 url
为 /login/impersonate
,默认注销切换账号的 url
为 /logout/impersonate
,默认的账号参数为 username
。
你可以通过此类实现自定义的账户切换。
所有内置的 31个过滤器作用都讲解完了, 有一些默认已经启用。有一些需要引入特定的包并且对 HttpSecurity
进行配置才会生效 。而且它们的顺序是既定的。 只有你了解这些过滤器你才能基于业务深度定制 Spring Security 。
关注公众号:Felordcn获取更多资讯
个人博客:https://felord.cn