Web 层(用于 UI 和 HTTP 后端)中的 Spring Security 基于 Servlet Filters
,因此通常首先了解 Filters
的作用会很有帮助。下图显示了单个 HTTP 请求的处理的典型分层。
客户端向应用发送请求,然后容器根据请求 URI 的路径确定对它应用哪些过滤器和哪个 servlet。一个 servlet 最多只能处理一个请求,但是过滤器形成一个链,因此它们是有序的,实际上,如果过滤器要处理请求本身,则可以否决链的其余部分。过滤器还可以修改下游过滤器和 Servlet 中使用的请求和/或响应。过滤器还可以修改下游过滤器和 Servlet 中使用的请求和/或响应。过滤器链的顺序非常重要,Spring Boot 通过两种机制对其进行管理:一种是 Filter
类型的 @Beans
可以具有 @Order
或实现 Ordered
,另一种是它们可以本身成为 FilterRegistrationBean
的一部分。顺序是其 API 的一部分。一些现成的过滤器定义了它们自己的常量,以帮助表明它们相对于彼此的顺序(例如,Spring Session 的 SessionRepositoryFilter
的 DEFAULT_ORDER
为 Integer.MIN_VALUE + 50
,这告诉我们它喜欢早一点在链中,但它不排除在此之前的其他过滤器)。
Spring Security 作为链中的单个 Filter
来安置,其具体类型为 FilterChainProxy
,种种原因很快就会变得显而易见。在 Spring Boot 应用中,安全过滤器是 ApplicationContext
中的 @Bean
,默认情况下会安装该过滤器,以便将其应用于每个请求。它安置在由 SecurityProperties.DEFAULT_FILTER_ORDER
定义的位置,该位置又由 FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER
锚定(Spring Boot 应用希望过滤器包装请求并修改其行为时期望的最大顺序)。但是,还有更多的功能:从容器的角度来看,Spring Security 是单个过滤器,但是在内部有其他过滤器,每个过滤器都扮演着特殊的角色。这里是一个全貌:
图二、Spring Security 是单个物理过滤器,但是将处理委托给一系列内部过滤器。
实际上,安全过滤器中甚至还有一层中间层:它通常以 DelegatingFilterProxy
的形式安装在容器中,而不必是 Spring @Bean
。代理委托给一个始终为 @Bean
的 FilterChainProxy
,通常使用固定名称 springSecurityFilterChain
。它是 FilterChainProxy
,它包含所有内部安全性逻辑,这些安全性逻辑在内部排列为一个或多个过滤器链。所有过滤器都具有相同的 API(它们都实现了 Servlet Spec 中的 Filter
接口),并且它们都有机会否决该链的其余部分。
可以有多个过滤器链,它们全部由 Spring Security 在同一顶级 FilterChainProxy
中管理,而对于容器来说都是未知的。Spring Security 过滤器包含一个过滤器链列表,并向与其匹配的第一个链发送请求。下图显示了基于匹配请求路径(/foo/**
在 /**
之前匹配)发生的调度。这是很常见的,但不是匹配请求的唯一方法。该调度过程的最重要特征,只有一个链处理过请求。
图三、Spring Security FilterChainProxy
将请求分派到匹配的第一个链。
没有自定义安全配置的纯 Spring Boot 应用具有多个(称为 n)过滤器链,其中通常 n = 6.前 (n-1)个链只是用来忽略静态资源模式,例如 /css/**
和 /**
,以及错误视图 /error
(路径可以由用户通过 security.ignored
进行控制。SecurityProperties
配置 bean)。最后一个链与捕获所有路径 /**
匹配,并且更活跃,包含用于身份验证、授权、异常处理、会话处理、标头写入等的逻辑。默认情况下,该链中共有十一个过滤器,但通常情况下,用户不必担心使用哪个过滤器以及何时使用。
注意:容易不知道 Spring Security 内部的所有过滤器这一事实非常重要,尤其是在 Spring Boot 应用中,默认情况下,所有
Filter
类型的@Bean
都会自动向容器注册。因此,如果要向安全链中添加自定义过滤器,则无需将其设置为@Bean
或将其包装在明确禁用容器注册的FilterRegistrationBean
中。
创建并定制过滤器链
Spring Boot 应用(带有 /**
请求匹配器的应用)中默认后备过滤链具有 SecurityProperties.BASIC_AUTH_ORDER
的预定义顺序。我们可以通过设置 security.basic.enabled=false
完全关闭它,也可以将其用作后备并仅以较低的顺序定义其他规则。为此,只需添加类型为 WebSecurityConfigurerAdapter
(或 WebSecurityConfigurer
)的 @Bean
并使用 @Order
来装配。例如:
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/foo/**")
...;
}
}
这个 bean 将导致 Spring Security 添加一个新的过滤器链并在回退之前对其进行排序。
与另一套资源相比,许多应用对一套资源的访问规则完全不同。例如,承载 UI 和支持 API 的应用可能支持基于 cookie 的身份验证以及对 UI 部件的登录页面的重定向,以及基于令牌的身份验证以及对 API 部件的未经身份验证的请求的 401 响应。每组资源都有其自己的 WebSecurityConfigurerAdapter
以及唯一的顺序和自己的请求匹配器。如果匹配规则重叠,则最早的有序过滤器链将获胜。
分发与授权请求匹配
安全筛选器链(或等效的 WebSecurityConfigurerAdapter
)具有请求匹配器,该请求匹配用于确定是否将其应用于 HTTP 请求。一旦决定应用特定的过滤器链,就不再应用其他过滤器链。但是在过滤器链中,可以通过在 HttpSecurity
配置器中设置其他匹配器来对授权进行更细粒度的控制。例如:
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/foo/**")
.authorizeRequests()
.antMatchers("/foo/bar").hasRole("BAR")
.antMatchers("/foo/spam").hasRole("SPAM")
.anyRequest().isAuthenticated();
}
}
配置 Spring Security 时最容易犯的一个错误是忘记将这些匹配器应用于不同的进程,一个是整个过滤器链的请求匹配器,另一个是仅选择要应用的访问规则。
将应用安全规则与 Actuator 规则绑定
如果我们将 Spring Boot Actuator 用于管理端点,则可能希望它们是安全的,默认情况下它们将是安全的。实际上,将执行器添加到安全应用后,我们会获得一条仅适用于执行器端点的附近加过滤器链。它由仅匹配执行器端点的请求匹配器定义,并且其顺序为 ManagementServerProperties.BASIC_AUTH_ORDER
,该顺序比默认的 SecurityProperties
后备过滤器少五个,因此在进行后备处理之前请先进行查询。
如果希望将应用安全规则用于执行器端点,则可以添加一个比执行器顺序更早订购的过滤器链,并带有一个包括所有执行器端点的请求匹配器。如果我们喜欢执行器端点的默认安全性设置,那么最简单的方法是在执行器端点之后但在回退之前(例如 ManagementServerProperties.BASIC_AUTH_ORDER + 1
)添加自己的过滤器。例如:
@Configuration
@Order(ManagementServerProperties.BASIC_AUTH_ORDER + 1)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/foo/**")
...;
}
}
注意:Web 层中的 Spring Security 当前与 Servlet API 绑定在一起,因此,它仅在以嵌入式或其它方式在 Servlet 容器中运行应用时才真正适用。但是,它不依赖于 Spring MVC 或 Spring Web 堆栈的其余部分,因此可以在任何 servlet 应用中使用,例如使用 JAX-RS 的 servlet 应用。