Spring Security: Persisting Authentication 持久化认证

312 阅读2分钟

用户第一次请求受保护的资源时,他们会被提示去进行认证。最常见的方式是将请求重定向到一个登录页。下面是一个换取登录凭证的简单示例:

  1. 请求一个受保护的资源。
  2. 重定向到登录页面。
  3. 用户输入用户名和密码。
  4. 认证通过后,给用户分配一个session。
  5. 后续请求会带着这个cookie进行身份认证。

SecurityContextRepository

在 Spring Security 中,后续的请求与用户的关联是通过 SecurityContextRepository 实现的。SecurityContextRepository 的默认实现是 DelegatingSecurityContextRepository ,它包含:

  • HttpSessionSecurityContextRepository
  • RequestAttributeSecurityContextRepository

HttpSessionSecurityContextRepository

HttpSessionSecurityContextRepositorySecurityContextHttpSession 关联起来。 如果我们希望用其他方式关联用户和后续的请求或者完全不关联,我们可以用 SecurityContextRepository 的其他实现来替换 HttpSessionSecurityContextRepository

NullSecurityContextRepository

如果不想将 SecurityContextHttpSession 关联起来,比如 使用 OAuth 认证。 NullSecurityContextRepository 是一个 SecurityContextRepository 的实现,它什么都不做。

RequestAttributeSecurityContextRepository

RequestAttributeSecurityContextRepositorySecurityContext 保存为请求属性,以确保 SecurityContext 可用于跨调度类型发生的单个请求,这些请求可能会清除 SecurityContext

比如,客户端发出了一个通过认证的请求,随后请求发生错误。根据容器的实现,错误意味着 SecurityContext 会被清除,然后执行错误分发。当执行错误分发的时候,却没有 SecurityContext 可以被解析。这也意味着错误页没有办法使用 SecurityContext 用作认证或者展示当前用户,除非 SecurityContext 被以某种方式持久化。

public SecurityFilterChain filterChain(HttpSecurity http) {
	http
		// ...
		.securityContext((securityContext) -> securityContext
			.securityContextRepository(new RequestAttributeSecurityContextRepository())
		);
	return http.build();
}

DelegatingSecurityContextRepository

DelegatingSecurityContextRepositorySecurityContext 保存到多个 SecurityContextRepository 的委托中。并且允许以特定的顺序从任意的委托中获取 SecurityContext

对此最有用的是使用以下示例进行配置,它允许同时使用 RequestAttributeSecurityContextRepository 和 HttpSessionSecurityContextRepository。

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
	http
		// ...
		.securityContext((securityContext) -> securityContext
			.securityContextRepository(new DelegatingSecurityContextRepository(
				new RequestAttributeSecurityContextRepository(),
				new HttpSessionSecurityContextRepository()
			))
		);
	return http.build();
}