此版本仍在开发中,尚未被视为稳定版本。如需最新稳定版本,请使用 Spring Security 7.0.4spring-doc.cadn.net.cn

持久化认证

当用户首次请求受保护的资源时,系统会提示其输入凭据。 提示用户输入凭据的最常见方式之一是将用户重定向到登录页面。 未认证用户请求受保护资源时的简化版 HTTP 交互过程可能如下所示:spring-doc.cadn.net.cn

示例 1. 未认证用户请求受保护的资源
GET / HTTP/1.1
Host: example.com
Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7b
HTTP/1.1 302 Found
Location: /login

用户提交其用户名和密码。spring-doc.cadn.net.cn

已提交用户名和密码
POST /login HTTP/1.1
Host: example.com
Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7b

username=user&password=password&_csrf=35942e65-a172-4cd4-a1d4-d16a51147b3e

在对用户进行身份验证后,系统会将用户关联到一个新的会话 ID,以防止会话固定攻击spring-doc.cadn.net.cn

已认证用户关联到新会话
HTTP/1.1 302 Found
Location: /
Set-Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8; Path=/; HttpOnly; SameSite=Lax

后续请求将包含会话 Cookie,该 Cookie 用于在会话的剩余时间内对用户进行身份验证。spring-doc.cadn.net.cn

以凭据形式提供的已认证会话
GET / HTTP/1.1
Host: example.com
Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8

安全上下文存储库

在 Spring Security 中,用户与后续请求的关联是通过 SecurityContextRepository 实现的。 SecurityContextRepository 的默认实现是 DelegatingSecurityContextRepository,它委托给以下内容:spring-doc.cadn.net.cn

HttpSessionSecurityContextRepository

The HttpSessionSecurityContextRepositorySecurityContextHttpSession 关联起来。 如果用户希望以其他方式或不关联用户与后续请求,可以将 HttpSessionSecurityContextRepository 替换为 SecurityContextRepository 的另一个实现。spring-doc.cadn.net.cn

空安全上下文仓库

如果不希望将 SecurityContextHttpSession 关联(例如在使用 OAuth 进行身份验证时),NullSecurityContextRepository 是一个不执行任何操作的 SecurityContextRepository 实现。spring-doc.cadn.net.cn

RequestAttributeSecurityContextRepository

The RequestAttributeSecurityContextRepositorySecurityContext 保存为请求属性,以确保 SecurityContext 在跨越可能清除 SecurityContext 的分发类型时,对单次请求可用。spring-doc.cadn.net.cn

例如,假设客户端发起一个请求,经过身份验证后发生了一个错误。 根据 Servlet 容器的具体实现,该错误会导致已建立的任何 SecurityContext 被清除,然后执行错误分发(error dispatch)。 当执行错误分发时,并未建立 SecurityContext。 这意味着,除非以某种方式持久化了 SecurityContext,否则错误页面无法使用它来进行授权或显示当前用户信息。spring-doc.cadn.net.cn

使用 RequestAttributeSecurityContextRepository
public SecurityFilterChain filterChain(HttpSecurity http) {
	http
		// ...
		.securityContext((securityContext) -> securityContext
			.securityContextRepository(new RequestAttributeSecurityContextRepository())
		);
	return http.build();
}
<http security-context-repository-ref="contextRepository">
	<!-- ... -->
</http>
<b:bean name="contextRepository"
	class="org.springframework.security.web.context.RequestAttributeSecurityContextRepository" />

委托安全上下文仓库

The DelegatingSecurityContextRepositorySecurityContext 保存到多个 SecurityContextRepository 委托中,并允许按指定顺序从任意委托中检索。spring-doc.cadn.net.cn

对于这种情况,最有用的配置方式如下所示,它允许同时使用 RequestAttributeSecurityContextRepositoryHttpSessionSecurityContextRepositoryspring-doc.cadn.net.cn

配置 DelegatingSecurityContextRepository
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
	http
		// ...
		.securityContext((securityContext) -> securityContext
			.securityContextRepository(new DelegatingSecurityContextRepository(
				new RequestAttributeSecurityContextRepository(),
				new HttpSessionSecurityContextRepository()
			))
		);
	return http.build();
}
@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
	http {
		// ...
		securityContext {
			securityContextRepository = DelegatingSecurityContextRepository(
				RequestAttributeSecurityContextRepository(),
				HttpSessionSecurityContextRepository()
			)
		}
	}
	return http.build()
}
<http security-context-repository-ref="contextRepository">
	<!-- ... -->
</http>
<bean name="contextRepository"
	class="org.springframework.security.web.context.DelegatingSecurityContextRepository">
		<constructor-arg>
			<bean class="org.springframework.security.web.context.RequestAttributeSecurityContextRepository" />
		</constructor-arg>
		<constructor-arg>
			<bean class="org.springframework.security.web.context.HttpSessionSecurityContextRepository" />
		</constructor-arg>
</bean>

在 Spring Security 6 中,上面所示的示例是默认配置。spring-doc.cadn.net.cn

安全上下文持久化过滤器

SecurityContextPersistenceFilter 负责使用 SecurityContextRepository 在请求之间持久化 SecurityContextspring-doc.cadn.net.cn

securitycontextpersistencefilter

number 1在运行应用程序的其余部分之前,SecurityContextPersistenceFilter加载SecurityContext来自SecurityContextRepository并将其设置于SecurityContextHolder.spring-doc.cadn.net.cn

number 2接下来,运行应用程序。spring-doc.cadn.net.cn

number 3最后,如果SecurityContext已更改,我们保存了SecurityContext使用SecurityContextRepository这意味着在使用SecurityContextPersistenceFilter,只需设置SecurityContextHolder将确保SecurityContext使用SecurityContextRepository.spring-doc.cadn.net.cn

在某些情况下,响应会在 SecurityContextPersistenceFilter 方法完成之前就被提交并写入客户端。 例如,如果向客户端发送了一个重定向,响应会立即写回客户端。 这意味着在第3步中将无法创建 HttpSession,因为会话ID无法包含在已经写入的响应中。 另一种可能发生的情况是:如果客户端成功认证,但在 SecurityContextPersistenceFilter 完成之前响应已被提交,且客户端在 SecurityContextPersistenceFilter 完成前发起了第二个请求,那么第二个请求中可能会出现错误的认证信息。spring-doc.cadn.net.cn

为避免这些问题,SecurityContextPersistenceFilter 会对 HttpServletRequestHttpServletResponse 进行包装,以检测 SecurityContext 是否发生了变化,如果发生变化,则在响应提交之前保存该 SecurityContextspring-doc.cadn.net.cn

SecurityContextHolderFilter

The SecurityContextHolderFilter 负责在请求之间使用 SecurityContextRepository 加载 SecurityContextspring-doc.cadn.net.cn

securitycontextholderfilter

number 1在运行应用程序的其余部分之前,SecurityContextHolderFilter加载SecurityContext来自SecurityContextRepository并将其设置于SecurityContextHolder.spring-doc.cadn.net.cn

number 2接下来,运行应用程序。spring-doc.cadn.net.cn

SecurityContextPersistenceFilter 不同,SecurityContextHolderFilter 仅加载 SecurityContext,而不会保存 SecurityContext。 这意味着在使用 SecurityContextHolderFilter 时,必须显式保存 SecurityContextspring-doc.cadn.net.cn

显式保存 SecurityContext
public SecurityFilterChain filterChain(HttpSecurity http) {
	http
		// ...
		.securityContext((securityContext) -> securityContext
			.requireExplicitSave(true)
		);
	return http.build();
}
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
    http {
        securityContext {
            requireExplicitSave = true
        }
    }
    return http.build()
}
<http security-context-explicit-save="true">
	<!-- ... -->
</http>

在使用该配置时,需要注意的是:任何将 SecurityContextHolder 设置到 SecurityContext 中的代码,如果希望该 SecurityContext 在请求之间持久化,还必须将其保存到 SecurityContextRepository 中。spring-doc.cadn.net.cn

例如,以下代码:spring-doc.cadn.net.cn

使用 SecurityContextHolder 设置 SecurityContextPersistenceFilter
SecurityContextHolder.setContext(securityContext);
SecurityContextHolder.setContext(securityContext)
使用 SecurityContextHolder 设置 SecurityContextHolderFilter
SecurityContextHolder.setContext(securityContext);
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse);
SecurityContextHolder.setContext(securityContext)
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse)