Spring Security
Spring Security
title:version
5.6.x
Spring Security 是一个提供认证、授权以及一些常见漏洞防护的框架。该框架为 Servlet 应用程序(Spring MVC)和响应式应用程序(Spring WebFlux,本文不表)提供防护,并作为防护 spring 项目的事实标准。
Servlet 应用程序
Spring Security 通过标准的 Servlet 过滤器 集成 Servlet 容器。这表明只要在 Servlet 容器中运行的应用程序都可以使用该框架。具体来说,你不需要在以 Servlet 为容器的应用程序中专门引入 Spring,就可以使用该框架。
在 Spring Boot 项目中使用 Spring Security
新建项目
build.gradle 中输入以下内容:
plugins {
id 'org.springframework.boot' version '2.6.7'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.github.toy'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
tasks.named('test') {
useJUnitPlatform()
}
运行
控制台输出
...
2022-04-30 16:21:28.626 WARN 37347 --- [ main] .s.s.UserDetailsServiceAutoConfiguration :
Using generated security password: fceb50df-05cf-40e4-a4c5-67d5da55a6b4
...
Spring Boot Auto Configuration
Spring Boot 自动处理以下内容:
- 启用 Spring Security 的默认配置,它会创建一个 servlet
过滤器bean,叫做springSecurityFilterChain。这个 bean 负责应用程序中的所有安全工作(保护应用程序的 URL,验证提交的用户名和密码,重定向登录表单,等等)。 - 用户名(username)为
user,密码为 console 中生成的随机密码,使用此账号和密码创建UserDetailsServicebean。 - 通过 Servlet 容器为每个请求注册名为
springSecurityFilterChain的过滤器。
Spring Boot 配置的内容不多,但是做的很多。功能总结如下:
- 与应用程序的任何交互都需要经过身份验证的用户
- 为你生成默认登录表单
- 使用用户名
user和在 console 中生成的密码,基于表单做身份认证(在刚才 console 中输出的密码为:fceb50df-05cf-40e4-a4c5-67d5da55a6b4) - 通过 BCrypt 加密密码,以此保护密码
- 用户可以退出登录
- 防止 CSRF(Cross-site request forgery,跨站请求伪造) 攻击
- Session Fixation 防护
- 集成安全头
- 用于防护请求的 HTTP Strict Transport Security
- 集成 X-Content-Type-Options
- 控制缓存(你的应用程序可以重写缓存,从而缓存你的静态资源)
- 集成 X-XSS-Protection
- 集成 X-Frame-Options 以阻止 Clickjacking
- 集成以下 Servlet API:
Spring Security 在 Servlet 应用程序中的顶层架构
我们将基于认证、授权、Protection Against Exploits 来理解这种顶层架构。
过滤器
Spring Security 的 Servlet 建立在 Servlet 的 过滤器 上,所以先查看 过滤器 的作用很有帮助。以下图片显示了单个请求的处理层级。

客户端给应用程序发送请求,容器创建一个包含过滤器和 Servlet 的 FilterChain ,它将处理基于请求 URI 路径的 HttpServletRequest。在 Spring MVC 应用程序中,Servlet 是 DispatcherServlet 的实例。一个 Servlet 最多只能处理一个 HttpServletRequest 和 HttpServletReponse。不过,可以使用多个 过滤器 处理以下内容:
- 阻止下游
过滤器或者Servlet被调用。在这个实例中,过滤器将写HttpServletReponse。 - 修改下游
过滤器和Servlet的HttpServletRequest和HttpServletReponse。
FilterChain 的能力来自于传入其中的 过滤器。
FilterChain 用例
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// 在应用程序处理前,做一些逻辑处理
chain.doFilter(request, response); // 调起应用程序处理
// 在应用程序处理后,做一些逻辑处理
}
因为 过滤器 只影响下游的 过滤器 和 Servlet,所以每个 过滤器 的调用顺序及其重要。
DelegatingFilterProxy
Spring 提供一个叫做 DelegatingFilterProxy 的实现,它桥接了 Servlet 容器的生命周期和 Spring 的 ApplicationContext。Servlet 容器允许其使用自己的标准注册 过滤器,但是它感应不到 Spring 定义的 bean。DelegatingFilterProxy 可以凭借 Servlet 容器的机制完成注册,但是所有的工作都委托给实现了 过滤器 的 Spring Bean。
以下是 DelegatingFilterProxy 如何融合 过滤器 和 FilterChain 的图。

DelegatingFilterProxy 从 ApplicationContext 中查找并调用 Filter0 Bean。DelegatingFilterProxy 的伪代码如下。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// 加载注册为 Spring Bean 的过滤器
// 例如在 DelegatingFilterProxy 中,delegate 是 Filter0 Bean 的实例
Filter delegate = getFilterBean(someBeanName);
// 将工作委托给 Spring Bean
delegate.doFilter(request, response);
}
DelegatingFilterProxy 的另一个好处是可以延迟查找 过滤器 的实例。这种方式十分重要,原因是容器需要在启动之前注册 过滤器。不过,Spring 一般通过 ContextLoaderListener 加载 Spring 的 Bean,直到所有的 过滤器 注册完才结束。
FilterChainProxy
对 Spring Security 的 Servlet 支持,包含在 FilterChainProxy 中。FilterChainProxy 是 Spring Security 提供的一种特殊 过滤器,它可以通过 SecurityFilterChain 委托很多 过滤器。因为 FilterChainProxy 是一个 Bean,所以它一般封装在 DelegatingFilterProxy 中。

SecurityFilterChain
SecurityFilterChain 被 FilterChainProxy 用于决定在该请求种哪个 Spring Security 的过滤器应该被调用。

Security Filters 在 SecurityFilterChain 一般以 Bean 的方式存在,它们通过 FilterChainProxy 注册,从而替代 DelegatingFilterProxy。FilterChainProxy 直接通过 Servlet 容器或 DelegatingFilterProxy 注册,提供了诸多好处。首先,它为 Spring Security 的 Servlet 提供支撑起点。因此,如果你要解决 Spring Security Servlet 的问题,在 FilterChainProxy 中添加 debug 断点是不错的开始。
第二,由于 FilterChainProxy 是使用 Spring Security 的重点,它可以执行一些必要的任务。例如,清除 SecurityContext,从而避免内存泄漏。还可以应用于 Spring Security 的 HttpFirewall,从而保护应用程序免受某些类型的攻击。
此外,在决定何时调用 SecurityFilterChain 时,它提供了更多的灵活性。在 Servlet 容器中,只基于 URL 调用 过滤器。不过,FilterChainProxy 通过利用 RequestMatcher 接口,可以根据 HttpServletRequest 中的任何事情决定调用。
事实上,FilterChainProxy 可以用于决定应该调用哪个 SecurityFilterChain 。因此可以为应用程序的不同切片提供一个总体的分隔配置。

在上图中,FilterChainProxy 决定应该使用哪个 SecurityFilterChain。第一个匹配的 SecurityFilterChain 会被调用。如果发起一个 URL 为 /api/message 的请求,它将匹配 SecurityFilterChain 0 的模式 /api/** ,所以只会调用 SecurityFilterChain 0 ,即使也匹配 SecurityFilterChain n 。如果发起一个 URL 为 /messages 的请求,它不匹配 SecurityFilterChain 0 的模式 /api/**,所以 FilterChainProxy 将继续尝试每一个 SecurityFilterChain。如果没有匹配到,SecurityFilterChain 匹配的 SecurityFilterChain n 实例将会被调用。
注意,SecurityFilterChain 0 只配置了 3 个安全 过滤器。但是,SecurityFilterChain n 配置了 4 个 过滤器 实例。每个 SecurityFilterChain 可以孤立的配置,留意这一点很重要。事实上,SecurityFilterChain 可能不配置 过滤器,如果 Spring Security 要忽略某些请求。
Security Filters
Security Filters 通过 SecurityFilterChain API 插入到 [[#FilterChainProxy]] 中。[[#过滤器]] 的顺序十分重要。通常,无需了解 Spring Security 过滤器 的顺序。但是,有些时候了解它们排序是有益的。
以下是 Spring Security 过滤器的综合排序:
- ChannelProcessingFilter
- SecurityContextPersistenceFilter
- WebAsyncManagerIntegrationFilter
- HeaderWriterFilter
- CorsFilter
- CsrfFilter
- LogoutFilter
- OAuth2AuthorizationRequestRedirectFilter
- Saml2WebSsoAuthenticationRequestFilter
- X509AuthenticationFilter
- AbstractPreAuthenticatedProcessingFilter
- CasAuthenticationFilter
- OAuth2LoginAuthenticationFilter
- Saml2WebSsoAuthenticationFilter
UsernamePasswordAuthenticationFilter- OpenIDAuthenticationFilter
- DefaultLoginPageGeneratingFilter
- DefaultLogoutPageGeneratingFilter
- ConcurrentSessionFilter
DigestAuthenticationFilter- BearerTokenAuthenticationFilter
BasicAuthenticationFilter- RequestCacheAwareFilter
- SecurityContextHolderAwareRequestFilter
- JaasApiIntegrationFilter
- RememberMeAuthenticationFilter
- AnonymousAuthenticationFilter
- OAuth2AuthorizationCodeGrantFilter
- SessionManagementFilter
ExceptionTranslationFilterFilterSecurityInterceptor- SwitchUserFilter
处理 Security 异常
ExceptionTranslationFilter 可以将 AccessDeniedException 和 AuthenticationException 转入 HTTP 响应中。
ExceptionTranslationFilter 作为 Security Filters 中一员插入到 [[#FilterChainProxy]] 中。

- 首先,
ExceptionTranslationFilter调用Filter.doFilter(requeset,response)以执行其余应用程序。 - 如果用户未认证或者是
AuthenticationException,那么开始认证。- 清除
SecurityContextHolder。 HttpServletRequest保存在RequestCache中。当用户成功认证后,RequestCache用于重现原始请求。AuthenticationEntryPoint用于来自客户端的请求凭证。例如,重定向到登录页面或者发送WWW-Authenticate头文件。
- 清除
- 另外,如果是
AccessDenieException,那么将拒绝访问。将唤起AccessDeniedHandler处理拒绝访问。
title: note
如果应用程序没有抛出 `AccessDeniedException` 或者 `AuthenticationException`,那么 `ExceptionTranslationFilter` 什么都不做。
ExceptionTranslationFilter 的伪代码如下:
try {
filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException ex) {
if (!authenticated || ex instanceof AuthenticationException) {
startAuthentication();
} else {
accessDenied();
}
}

浙公网安备 33010602011771号