书接上文,继续官网的个人翻译和个人理解,有不对的请见谅。第一个篇博客中写到Sevlet appliation的总体架构,本博客是写Sevlet appliation中Authentication的架构,在后面第三篇博客将会写到新版spring security如何通过数据库进行认证,对这篇的一个实战。
Spring Security 为身份验证提供了全面的支持。首先讨论整体的 Servlet Authentication体系结构。
提供了多种认证机制:
Username and Password?- 用户名和密码的认证机制
OAuth 2.0 Login?- 可以使用Open ID connect OAuth 2.0 登录?和非标准OA 2.0登录(如GitHub)
SAML 2.0 Login?- SAML 2.0 登录
Central Authentication Server (CAS)?- Central Authentication Server (CAS) 中心认证服务器支持
Remember Me?- how to remember a user past session expiration 如何记住用户之前会话过期的信息
JAAS Authentication?- 使用JAAS进行认证
Pre-Authentication Scenarios?- 使用外部机制(如 SiteMinder 或 Java EE 安全性)进行身份验证,但仍使用 Spring Security 进行授权和防止常见漏洞。
X509 Authentication?- X509 认证
Servlet Security的扩展。下图描述 Servlet 身份验证中使用的 Spring Security 的主要架构组件。如果需要具体的流程来解释这些部分如何组合在一起,请查看特定于身份验证机制的部分。
这边有许多名词解释:
Spring Security 身份验证模型的核心是 SecurityContextHolder。它包含 SecurityContext。
?SecurityContextHolder 是 Spring Security 存储身份验证者详细信息的位置。Spring Security 不关心 SecurityContextHolder 的填充方式。如果它包含一个值,则将其用作当前经过身份验证的用户。
一个最简单方法表明一个用户已通过身份验证是直接设置 SecurityContextHolder:
?
SecurityContext context = SecurityContextHolder.createEmptyContext(); //1
Authentication authentication =
new TestingAuthenticationToken("username", "password", "ROLE_USER"); //2
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context); //3
1.首先创建一个空的 SecurityContext。应该创建一个新的 SecurityContext 实例,而不是使用 SecurityContextHolder.getContext().setAuthentication(authentication),以避免跨多个线程的争用条件。
2.接下来,创建一个新的 Authentication 对象。Spring Security 不关心在 SecurityContext 上设置了什幺类型的 Authentication 实现。这里,使用 TestingAuthenticationToken,因为它非常简单。实际上项目最常见是 UsernamePasswordAuthenticationToken(userDetails, password, authorities),就是前端发过来账号密码认证。
3.最后,我们在 SecurityContextHolder 上设置 SecurityContext。Spring Security 使用此信息进行授权。
若要获取有关经过身份验证的主体的信息,请访问 SecurityContextHolder。
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
默认情况下,SecurityContextHolder 使用 ThreadLocal 来存储这些详细信息,这意味着 SecurityContext 始终可用于同一线程中的方法,即使 SecurityContext 未作为参数显式传递给这些方法也是如此。如果在处理当前主体的请求后注意清除线程,则以这种方式使用 ThreadLocal 是非常安全的。Spring Security 的 FilterChainProxy 确保始终清除 SecurityContext。
某些应用进程并不完全适合使用 ThreadLocal,因为它们使用线程的特定方式。例如,Swing 客户端可能希望 Java 虚拟机中的所有线程都使用相同的安全上下文。您可以使用启动时的策略配置 SecurityContextHolder,以指定您希望如何存储上下文。对于独立应用进程,您将使用SecurityContextHolder.MODE_GLOBAL策略。其他应用进程可能希望由安全线程生成的线程也采用相同的安全标识。您可以通过使用 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL 来实现此目的。您可以通过两种方式更改默认SecurityContextHolder.MODE_THREADLOCAL模式。第一种是设置系统属性。第二种是在 SecurityContextHolder 上调用静态方法。大多数应用进程不需要更改默认值。但是,如果您这样做,请查看 SecurityContextHolder 的 JavaDoc 以了解更多信息。
从SecurityContextHolder获取,存储Authentication的对象。
Authentication 接口在 Spring Security 中有两个主要用途:
Authentication包含:
?GrantedAuthority 实例是授予用户的高级权限。2个例子就是:角色和作用域。
可以从 Authentication.getAuthorities()方法获取 GrantedAuthority 实例。此方法提供 GrantedAuthority 对象的集合。毫不奇怪,GrantedAuthority 是授予委托人的权限。这些权力通常是“角色”,例如ROLE_ADMINISTRATOR或ROLE_HR_SUPERVISOR。稍后将针对 Web 授权、方法授权和域对象授权配置这些角色。Spring Security 的其他部分会解析这些权限,并将预期情况展示出来。使用基于用户名/密码的身份验证时,GrantedAuthority 实例通常由 UserDetailsService 加载。
通常,GrantedAuthority 对象是应用进程范围的权限。它们不特定于给定的域对象。因此,您不太可能使用 GrantedAuthority 来表示对 Employee 对象编号 54 的权限,因为如果有数千个这样的权限,您将很快耗尽内存(或者至少会导致应用进程需要很长时间才能对用户进行身份验证)。当然,Spring Security 是专门为处理这一常见需求而设计的,但您应该为此目的使用项目的域对象安全功能。
AuthenticationManager 是定义 Spring Security 的过滤器如何执行身份验证的 API。然后,调用 AuthenticationManager 的控制器(即 Spring Security 的 Filters 实例)在 SecurityContextHolder 上设置返回的 Authentication。如果不与 Spring Security 的 Filters 实例集成,则可以直接设置 SecurityContextHolder,而不需要使用 AuthenticationManager。
虽然 AuthenticationManager 的实现可以是任何内容,但最常见的实现是 ProviderManager。
ProviderManager 是最常用的 AuthenticationManager 实现。ProviderManager 委托给 AuthenticationProvider 实例的 List。每个 AuthenticationProvider 都有机会指示身份验证应成功、失败,或指示它无法做出决定并允许下游 AuthenticationProvider 做出决定。如果配置的所有 AuthenticationProvider 实例都无法进行身份验证,则身份验证将失败,并出现 ProviderNotFoundException,这是一个特殊的 AuthenticationException,指示 ProviderManager 未配置为支持传递到其中的身份验证类型。
?
实际上,每个 AuthenticationProvider 都知道如何执行特定类型的身份验证。例如,一个 AuthenticationProvider 可能能够验证用户名/密码,而另一个 AuthenticationProvider 可能能够验证 SAML 断言。这允许每个 AuthenticationProvider 执行非常特定类型的身份验证,同时支持多种类型的身份验证,并且仅公开单个 AuthenticationManager bean。
ProviderManager 还允许配置可选的父 AuthenticationManager,如果没有 AuthenticationProvider 可以执行身份验证,则会参考该父 AuthenticationManager。父级可以是任何类型的 AuthenticationManager,但它通常是 ProviderManager 的实例。
?
?事实上,多个 ProviderManager 实例可能共享同一个父 AuthenticationManager。这在有多个 SecurityFilterChain 实例具有一些共同的身份验证(共享父 AuthenticationManager)但也有不同的身份验证机制(不同的 ProviderManager 实例)的情况下很常见。
默认情况下,ProviderManager 会尝试从成功的身份验证请求返回的 Authentication 对象中清除任何敏感凭据信息。这样可以防止信息(如密码)在 HttpSession 中的保留时间超过必要的时间。
当您使用用户对象的缓存时,这可能会导致问题,例如,提高无状态应用进程中的性能。如果 Authentication 包含对缓存中某个对象(如 UserDetails 实例)的引用,并且该对象删除了其凭据,则无法再针对缓存的值进行身份验证。如果使用缓存,则需要考虑到这一点。一个明显的解决方案是首先在缓存实现或创建返回的 Authentication 对象的 AuthenticationProvider 中创建对象的副本。或者,可以禁用 ProviderManager 上的 eraseCredentialsAfterAuthentication 属性。请参阅 Javadoc 中 的 Javadoc 类。
?可以将多个 AuthenticationProviders 实例注入 ProviderManager。每个 AuthenticationProvider 都执行特定类型的身份验证。例如,DaoAuthenticationProvider 支持基于用户名/密码的身份验证,而 JwtAuthenticationProvider 支持对 JWT 令牌进行身份验证。
?AuthenticationEntryPoint 用于发送从客户端请求凭据的 HTTP 响应。
有时,客户端会主动包含凭据(例如用户名和密码)来请求资源。在这些情况下,Spring Security 不需要再向客户端提供HTTP响应,请求客户端提供凭据,因为它们已经包含请求中了。
在其他情况下,客户端会向无权访问的资源发出未经身份验证的请求。在这种情况下,AuthenticationEntryPoint 的实现用于从客户端请求凭据。AuthenticationEntryPoint 实现可能会执行到登录页的重定向、使用 WWW-Authenticate 标头进行响应或执行其他操作。
?AbstractAuthenticationProcessingFilter 用作验证用户凭据的基本过滤器。在对凭据进行身份验证之前,Spring Security 通常使用 AuthenticationEntryPoint 请求凭据。
接下来,AbstractAuthenticationProcessingFilter 可以对提交给它的任何身份验证请求进行身份验证。
1.当用户提交其凭据时,AbstractAuthenticationProcessingFilter 从要进行身份验证的 HttpServletRequest 创建一个身份验证。创建的 Authentication 类型取决于 AbstractAuthenticationProcessingFilter 的子类。例如,UsernamePasswordAuthenticationFilter 根据 HttpServletRequest 中提交的用户名和密码创建 UsernamePasswordAuthenticationToken。
2.接下来,将 Authentication 传递到 AuthenticationManager 中进行身份验证。
3.如果身份验证失败,则失败。
4.如果身份验证成功,则成功。
调用 AuthenticationSuccessHandler。请参阅 AuthenticationSuccessHandler 接口。