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 中生成的随机密码,使用此账号和密码创建UserDetailsService
bean。 - 通过 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
ExceptionTranslationFilter
FilterSecurityInterceptor
- 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();
}
}