Spring Security Servlet认证架构

本文描述 Servlet 身份验证中使用的 Spring Security 的主要架构组件。

  • SecurityContextHolder - SecurityContextHolder 是 Spring Security 存储身份验证者的详细信息的位置。
  • SecurityContext - 从 SecurityContextHolder 获取,包含当前已验证用户的 Authentication
  • Authentication - 可以是 AuthenticationManager 的输入,以提供用户已提供的身份验证凭据,也可以是 SecurityContext 中的当前用户。
  • GrantedAuthority - 在 Authentication 上授予主体的权限(即角色、范围等)
  • AuthenticationManager - 定义 Spring Security 的 Filters 如何执行身份验证的 API。
  • ProviderManager - AuthenticationManager 最常见的实现。
  • AuthenticationProvider - 由 ProviderManager 用于执行特定类型的身份验证。
  • Request Credentials with AuthenticationEntryPoint - 用于从客户端请求凭证(即重定向到登录页面,发送 WWW-Authenticate 响应等)
  • AbstractAuthenticationProcessingFilter - 用于身份验证的基础 Filter 。这也提供了一个很好的想法,高层次的身份验证流程和各部分如何协同工作。

SecurityContextHolder

Spring Security 身份验证模型的核心是 SecurityContextHolder 。它包含 SecurityContext。

SecurityContextHolder 是 Spring Security 存储谁被认证的详细信息的地方。Spring Security 不关心 SecurityContextHolder 是如何填充的。如果它包含一个值,则将其用作当前经过身份验证的用户。

指示用户已通过身份验证的最简单方法是直接设置 SecurityContextHolder

java 复制代码
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
    new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);

SecurityContextHolder.setContext(context);
  1. 我们先创建一个空的 SecurityContext 。您应该创建一个新的 SecurityContext 实例,而不是使用 SecurityContextHolder.getContext().setAuthentication(authentication) ,以避免多个线程之间的竞争条件。
  2. 接下来,我们创建一个新的 Authentication 对象。Spring Security 不关心在 SecurityContext 上设置了什么类型的 Authentication 实现。在这里,我们使用 TestingAuthenticationToken ,因为它非常简单。更常见的生产场景是 UsernamePasswordAuthenticationToken(userDetails, password, authorities)
  3. 最后,我们将 SecurityContext 设置在 SecurityContextHolder 上。Spring Security 使用此信息进行授权。

默认情况下, SecurityContextHolder 使用 ThreadLocal 来存储这些细节,这意味着 SecurityContext 始终可用于同一线程中的方法,即使 SecurityContext 没有显式地作为参数传递给这些方法。如果在当前主体的请求被处理之后注意清除线程,那么以这种方式使用 ThreadLocal 是非常安全的。Spring Security 的FilterChainProxy 确保 SecurityContext 始终被清除。

有些应用程序并不完全适合使用 ThreadLocal ,因为它们使用线程的特定方式。例如,Swing客户端可能希望Java虚拟机中的所有线程使用相同的安全上下文。您可以在启动时使用策略配置 SecurityContextHolder ,以指定您希望如何存储上下文。对于独立的应用程序,您可以使用 SecurityContextHolder.MODE_GLOBAL 策略。其他应用程序可能希望安全线程派生的线程也采用相同的安全标识。你可以通过使用 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL 来实现。您可以通过两种方式更改默认的 SecurityContextHolder.MODE_THREADLOCAL 模式。第一个是设置系统属性。第二个是在 SecurityContextHolder 上调用一个静态方法。大多数应用程序不需要更改默认值。

SecurityContext

从 SecurityContextHolder 获取 SecurityContextSecurityContext 包含一个 Authentication 对象。

Authentication

Authentication 接口在 Spring Security 中有两个主要用途:

  • 输入 AuthenticationManager 以提供用户已提供用于身份验证的凭据。在这种情况下, isAuthenticated() 返回 false
  • 表示当前经过身份验证的用户。您可以从 SecurityContext 获取当前的 Authentication

Authentication 包含:

  • principal :识别用户。当使用用户名/密码进行身份验证时,这通常是 UserDetails 的实例。
  • credentials :通常是密码。在许多情况下,这是在用户通过身份验证后清除的,以确保它不会泄露。
  • authoritiesGrantedAuthority 实例是授予用户的高级权限。常见的两个权限是角色和范围。

GrantedAuthority

GrantedAuthority 实例是授予用户的高级权限。常见的两个权限是角色和范围。

您可以通过 Authentication.getAuthorities() 方法获取 GrantedAuthority 实例。此方法返回 GrantedAuthority 对象的集合。 GrantedAuthority 是授予委托人的权限,这些权限通常是"角色",例如 ROLE_ADMINISTRATORROLE_HR_SUPERVISOR 。这些角色稍后会针对Web授权、方法授权和域对象授权进行配置。Spring Security 的其他部分解释这些权限并期望它们存在。当使用基于用户名/密码的身份验证时, GrantedAuthority 实例通常由 UserDetailsService 加载。

AuthenticationManager

AuthenticationManager 是定义 Spring Security 的 Filters 如何执行身份验证的 API。返回的 Authentication 然后由调用 AuthenticationManager 的控制器(即Spring Security的 Filters 实例)在 SecurityContextHolder 上设置。如果您没有集成 Spring Security 的 Filters 实例,则可以直接设置 SecurityContextHolder ,而不需要使用 AuthenticationManager

虽然 AuthenticationManager 的实现可以是任何东西,但最常见的实现是 ProviderManager

ProviderManager

ProviderManagerAuthenticationManager 最常用的实现。 ProviderManager 委托给 AuthenticationProvider 实例中的 List 实例。每个 AuthenticationProvider 都有机会指示身份验证应该成功、失败,或者指示它不能做出决定并允许下游 AuthenticationProvider 做出决定。如果配置的 AuthenticationProvider 实例都不能进行身份验证,则身份验证失败,并显示 ProviderNotFoundException ,这是一个特殊的 AuthenticationException ,表示 ProviderManager 未配置为支持传入其中的 Authentication 类型。

实际上,每个 AuthenticationProvider 都知道如何执行特定类型的身份验证。例如,一个 AuthenticationProvider 可能能够验证用户名/密码,而另一个可能能够验证SAML断言。这使得每个 AuthenticationProvider 都可以进行一种非常特定的身份验证,同时支持多种类型的身份验证,并且只公开一个 AuthenticationManager bean。

ProviderManager 还允许配置一个可选的父级 AuthenticationManager ,在没有 AuthenticationProvider 可以执行身份验证的情况下会参考该父级。父类可以是任何类型的 AuthenticationManager ,但它通常是 ProviderManager 的实例。

事实上,多个 ProviderManager 实例可能共享同一个父级 AuthenticationManager 。这在多个 SecurityFilterChain 实例具有一些共同的身份验证(共享父级 AuthenticationManager ),但也具有不同的身份验证机制(不同的 ProviderManager 实例)的场景中有些常见。

默认情况下, ProviderManager 尝试从成功的身份验证请求返回的 Authentication 对象中清除任何敏感凭据信息。这可以防止信息(如密码)在 HttpSession 中保留的时间超过必要的时间。

这可能会在您使用用户对象的缓存时导致问题,例如,在无状态应用程序中提高性能。如果 Authentication 包含对该高速缓存中某个对象的引用(例如 UserDetails 实例),并且该对象的凭据已被删除,则无法再根据缓存值进行身份验证。如果使用缓存,您需要考虑到这一点。一个显而易见的解决方案是首先在该高速缓存实现中或在创建返回的 Authentication 对象的 AuthenticationProvider 中复制对象。或者,您可以禁用 ProviderManager 上的 eraseCredentialsAfterAuthentication 属性。

AuthenticationProvider

您可以将多个 AuthenticationProvider 实例注入到 ProviderManager 中。每个 AuthenticationProvider 执行特定类型的身份验证。例如, DaoAuthenticationProvider 支持基于用户名/密码的身份验证,而 JwtAuthenticationProvider 支持对JWT令牌进行身份验证。

Request Credentials with AuthenticationEntryPoint

AuthenticationEntryPoint 用于发送请求客户端凭据的HTTP响应。

有时,客户端主动包括凭据(例如用户名和密码)以请求资源。在这些情况下,Spring Security 不需要提供从客户端请求凭据的HTTP响应,因为它们已经包含在内。

在其他情况下,客户端对他们无权访问的资源发出未经身份验证的请求。在这种情况下,使用 AuthenticationEntryPoint 的实现从客户端请求凭证。 AuthenticationEntryPoint 实现可能会执行重定向到登录页面,使用WWW-Authenticate头进行响应,或采取其他操作。

AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter 用作用于验证用户凭据的基础 Filter 。在认证凭证之前,Spring Security 通常使用 AuthenticationEntryPoint 请求凭证。

接下来, AbstractAuthenticationProcessingFilter 可以验证提交给它的任何身份验证请求。

  1. 当用户提交他们的凭证时, AbstractAuthenticationProcessingFilter 从要验证的 HttpServletRequest 创建一个 Authentication 。创建的 Authentication 的类型依赖于 AbstractAuthenticationProcessingFilter 的子类。例如, UsernamePasswordAuthenticationFilterHttpServletRequest 中提交的用户名和密码创建 UsernamePasswordAuthenticationToken
  2. 接下来, Authentication 被传递到 AuthenticationManager 以进行身份验证。
  3. 如果认证失败,则为failure。
    • SecurityContextHolder 被清除。
    • RememberMeServices.loginFail 被调用。如果没有配置remember me,则此操作无效
    • AuthenticationFailureHandler 被调用。
  4. 如果认证成功,则为success。
    • SessionAuthenticationStrategy 收到新登录的通知。
    • 在 SecurityContextHolder 上设置身份验证。之后,如果您需要保存 SecurityContext 以便可以在将来的请求中自动设置它,SecurityContextRepository#saveContext则必须显式调用。
    • RememberMeServices.loginSuccess 被调用。如果没有配置remember me,此操作无效。
    • ApplicationEventPublisher 发布一个 InteractiveAuthenticationSuccessEvent
      uccess` 被调用。如果没有配置remember me,此操作无效。
    • ApplicationEventPublisher 发布一个 InteractiveAuthenticationSuccessEvent
    • AuthenticationSuccessHandler 被调用。
相关推荐
一只爱打拳的程序猿几秒前
【Spring】更加简单的将对象存入Spring中并使用
java·后端·spring
阿伟*rui2 分钟前
认识微服务,微服务的拆分,服务治理(nacos注册中心,远程调用)
微服务·架构·firefox
ZHOU西口34 分钟前
微服务实战系列之玩转Docker(十八)
分布式·docker·云原生·架构·数据安全·etcd·rbac
deephub3 小时前
Tokenformer:基于参数标记化的高效可扩展Transformer架构
人工智能·python·深度学习·架构·transformer
ajsbxi3 小时前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet
鹿屿二向箔4 小时前
基于SSM(Spring + Spring MVC + MyBatis)框架的咖啡馆管理系统
spring·mvc·mybatis
架构师那点事儿4 小时前
golang 用unsafe 无所畏惧,但使用不得到会panic
架构·go·掘金技术征文
NoneCoder4 小时前
Java企业级开发系列(1)
java·开发语言·spring·团队开发·开发
W Y6 小时前
【架构-37】Spark和Flink
架构·flink·spark
Gemini19957 小时前
分布式和微服务的区别
分布式·微服务·架构