Spring Security提供了对身份验证的全面支持。本部分内容包含:
-
架构组件:Spring Security在Servlet身份验证中使用的主要架构组件
- SecurityContextHolder -
SecurityContextHolder
是Spring Security存储身份认证的详细信息的地方。 - SecurityContext -从
SecurityContextHolder
中获取,包含当前认证用户的Authentication
信息。 - Authentication - 可以是
AuthenticationManager
的输入,以提供用户已提供的用于身份验证的凭据,也可以是来自SecurityContext
的当前用户。 - GrantedAuthority - 在身份验证中授予主体的权限
- AuthenticationManager - 定义Spring Security的
Filter
如何执行认证
的API。 - ProviderManager -
AuthenticationManager
最常见的实现 - AuthenticationProvider - 由
ProviderManager
用于执行特定类型的身份认证。 - Request Credentials with
AuthenticationEntryPoint
- 用于从客户端请求凭证(即重定向到一个登录页面,发送一个WWW-Authenticate
响应,等等)。 - AbstractAuthenticationProcessingFilter - 用于身份验证的基本
Filter
。这还可以很好地了解高层次的身份验证流程以及各个部分如何协同工作。
- SecurityContextHolder -
-
认证机制
- Username and Password - 如何使用用户名和密码认证
- OAuth 2.0 Login - OAuth 2.0使用OpenID登录连接和非标准的OAuth 2.0登录
- SAML 2.0 Login - SAML 2.0 登录
- Central Authentication Server (CAS) - Central Authentication Server (CAS) 支持
- Remember Me - 如何记住用户会话过期
- JAAS Authentication - JAAS 认证
- OpenID - OpenID 认证 (不要与OpenID连接混淆)
- Pre-Authentication Scenarios - 使用外部机制(如
SiteMinder
或Java EE安全性)进行身份验证,但仍然使用Spring security进行授权和保护,以防止常见攻击。 - X509 Authentication - X509 认证
SecurityContextHolder
Spring Security的身份验证模型的核心是SecurityContextHolder
。它包含SecurityContext
。
SecurityContextHolder
是Spring Security存储身份验证的详细信息的地方。Spring Security并不关心SecurityContextHolder
是如何构成的。如果它包含一个值,那么它就被用作当前经过身份验证的用户。
配置SecurityContextHolder
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
程序中获取SecurityContextHolde
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虚拟机中的所有线程都使用相同的 security context。SecurityContextHolder
可以在启动时配置一个策略,以指定您希望如何存储上下文。对于独立的应用程序,将使用SecurityContextHolder.MODE_GLOBAL
策略。其他应用程序可能希望安全线程生成的线程也采用相同的安全标识。这是通过使用SecurityContexHolder .MODE_INHERITABLETHREADLOCAL
实现的。 -
可以通过两种方式更改默认的
SecurityContextHolder.MODE_THREADLOCAL
。第一个是设置系统属性,第二个是调用SecurityContextHolder上的静态方法。
- 大多数应用程序不需要更改默认设置
SecurityContext
SecurityContext
从SecurityContextHolder
中获取。SecurityContext
包含一个Authentication
对象。
Authentication
在Spring Security中,Authentication
有两个主要目的:
-
AuthenticationManager
的一个输入,用于提供用户为进行身份验证而提供的凭据。在此场景中使用时,isAuthenticated()
返回false
。 -
表示当前通过身份验证的用户。当前的
Authentication
可以从SecurityContext
中获取。
Authentication
包含:
principal
- 识别用户。当使用用户名/密码进行身份验证时,这通常是UserDetails
的一个实例。credentials
- 通常一个密码。在许多情况下,这将在用户身份验证后清除,以确保不会泄漏。authorities
-GrantedAuthority
是授予用户的高级权限。例如角色或作用域。
GrantedAuthority
GrantedAuthority
为用户被授予的高级权限。
-
GrantedAuthority
可以从Authentication.getAuthorities()
方法中获取. -
权限的
角色
表示,例如ROLE_ADMINISTRATOR
或ROLE_HR_SUPERVISOR
-
使用用户名/密码的身份验证时,
GrantedAuthority
通常由UserDetailsService
加载 -
通常权限是应用范围内的,没有给定对象的权限 (例如 特定id的User对象的权限)
AuthenticationManager
AuthenticationManager
定义了Spring Security的Filter如何进行认证。
常用实现类为ProviderManager
ProviderManager
ProviderManager
是AuthenticationManager
最常用的实现。
ProviderManager
委托给AuthenticationProvider
列表,
- 每个
AuthenticationProvider
都有机会表明身份验证应该是成功的,失败的, - 或者表明它不能做出决定,并允许下游的
AuthenticationProvider
来做出决定 - 如果没有
AuthenticationProvider
可以认证,ProviderNotFoundException
则抛出异常
实际上,每个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
属性。
AuthenticationProvider
- 多个
AuthenticationProviders
可以被注入到ProviderManager
中。 - 每个
AuthenticationProvider
执行特定类型的身份验证。例如,DaoAuthenticationProvider
支持基于用户名/密码的身份验证,而JwtAuthenticationProvider
支持验证JWT令牌。
AuthenticationEntryPoint
AuthenticationEntryPoint
用于发送HTTP响应,该响应从客户端请求凭据。
AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter
被用作验证用户凭据的基本Filter
。在验证凭据之前,Spring Security通常使用AuthenticationEntryPoint
请求凭据。接下来,AbstractAuthenticationProcessingFilter
可以验证提交给它的任何身份验证请求。
①当用户提交他们的凭据时,AbstractAuthenticationProcessingFilter
从HttpServletRequest
创建一个身份验证来进行身份验证。创建的身份验证类型依赖于AbstractAuthenticationProcessingFilter
的子类。例如,UsernamePasswordAuthenticationFilter
从HttpServletRequest
中提交的用户名和密码创建UsernamePasswordAuthenticationToken
。
②Authentication
交给AuthenticationManager
去进行认证
③认证失败,依次执行:
SecurityContextHolder
清理RememberMeServices.loginFail
开始执行,如果没有配置则无操作AuthenticationFailureHandler
执行
④认证成功,依次执行:
- SessionAuthenticationStrategy接受到新的登录通知
Authentication 配置到 SecurityContextHolder
中,之后SecurityContextPersistenceFilter
将SecurityContext
保存到HttpSession
.RememberMeServices.loginSuccess
执行,没有配置则无操作ApplicationEventPublisher
发布一个InteractiveAuthenticationSuccessEvent
。
Username/Password Authentication
这部分内容较多,参考我的另一篇文章 https://console.blog.csdn.net/article/details/116464236
Session Management
HTTP会话相关的功能是通过SessionManagementFilter
和SessionAuthenticationStrategy
接口的组合来处理的,该接口由过滤器委托给它。典型的应用包括会话固定保护、攻击预防、会话超时检测和限制通过身份验证的用户可以同时打开的会话数量。
可以实现下面功能
- 检测超时
- 并发会话控制
- 会话固定攻击防护
Remember-Me认证
记住我或持久登录身份验证指的是网站能够在会话之间记住主体的身份。这通常是通过向浏览器发送cookie来完成的,在未来的会话中检测到cookie可以自动登录。
Spring Security有两个具体的remember-me实现。
- 一个使用散列来保护基于cookie的令牌的安全性,
- 使用数据库或其他持久存储机制来存储生成的令牌。
注意,两个实现都需要一个UserDetailsService
。如果您使用的身份验证提供程序不使用UserDetailsService
(例如,LDAP提供程序),那么它将无法工作,除非您的应用程序上下文中也有UserDetailsService
bean。
基于Hash的Token
cookie组成
base64(username + ":" + expirationTime + ":" +
md5Hex(username + ":" + expirationTime + ":" password + ":" + key))
username: As identifiable to the UserDetailsService
password: That matches the one in the retrieved UserDetails
expirationTime: The date and time when the remember-me token expires, expressed in milliseconds
key: A private key to prevent modification of the remember-me token
因此,remember-me令牌仅在指定的时间段内有效,并且前提是用户名、密码和密钥不发生更改。值得注意的是,这有一个潜在的安全问题,因为捕获的remember-me令牌在令牌过期之前对任何用户代理都是可用的。这与摘要身份验证是相同的问题。如果主体意识到一个令牌已经被捕获,那么他们可以很容易地更改自己的密码,并立即使所有remember-me令牌失效。
持久化Token
需要配置数据源
数据库表
create table persistent_logins (username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null)
相关接口和实现
TokenBasedRememberMeServices
此实现支持简单哈希令牌方法中描述的更简单的方法。TokenBasedRememberMeServices
生成一个RememberMeAuthenticationToken
,该token由RememberMeAuthenticationProvider
处理。一个key
在这个身份验证提供者和TokenBasedRememberMeServices
之间共享。此外,TokenBasedRememberMeServices
需要一个UserDetailsService
,它可以从中检索用户名和密码,用于签名比较,并生成RememberMeAuthenticationToken
来包含正确的GrantedAuthority
。应用程序应该提供某种注销命令,当用户请求时使cookie失效。TokenBasedRememberMeServices
也实现了Spring Security的LogoutHandler
接口,因此可以与LogoutFilter
一起使用来自动清除cookie。
PersistentTokenBasedRememberMeServices
这个类可以以与TokenBasedRememberMeServices
相同的方式使用,但是它还需要配置一个PersistentTokenRepository
来存储令牌。有两种标准实现。
InMemoryTokenRepositoryImpl
内存JdbcTokenRepositoryImpl
数据库
OpenID支持
Anonymous Authentication匿名认证
Pre-Authentication
有些情况下,您希望使用Spring Security进行授权,但是用户在访问应用程序之前已经通过某些外部系统的可靠身份验证。我们将这些情况称为“预先身份验证”场景。例如X.509、Siteminder和应用程序在其中运行的Java EE容器的身份验证。当使用预认证时,Spring Security必须
- 识别发出请求的用户
- 获取用户的权限。
具体细节将取决于外部身份验证机制。对于X.509,用户可以通过证书信息来标识,对于Siteminder,则可以通过HTTP请求头来标识。如果依赖容器身份验证,将通过调用传入HTTP请求的getUserPrincipal()方法来标识用户。在某些情况下,外部机制可能为用户提供角色/权限信息,但在其他情况下,权限必须从单独的来源获得,如UserDetailsService。
JAAS Provider
JAAS
: Java Authentication and Authorization Service
Spring Security提供了一个能够将身份验证请求委托JAAS的包。
CAS 认证
JA-SIG在系统上产生一个企业范围的单点登录,称为CAS。与其他方法不同的是,JA-SIG的中央认证服务是开源的、被广泛使用、易于理解、平台独立并支持代理功能。Spring Security完全支持CAS,并提供了从Spring Security的单一应用程序部署到由企业范围的CAS服务器保护的多应用程序部署的简单迁移路径。
CAS参考资料https://www.apereo.org
Spring Security 中CAS认证参考资料 https://docs.spring.io/spring-security/site/docs/5.3.9.RELEASE/reference/html5/#servlet-cas
X.509认证
X.509证书身份验证最常用的用法是在使用SSL时验证服务器的身份,最常用的用法是在浏览器中使用HTTPS时。浏览器将自动检查服务器提供的证书是否已由它所维护的可信证书颁发机构之一颁发(即数字签名)。
参考资料 https://docs.spring.io/spring-security/site/docs/5.3.9.RELEASE/reference/html5/#servlet-x509
Run-As Authentication Replacement
AbstractSecurityInterceptor
能够在安全对象回调阶段临时替换SecurityContextHolder
和SecurityContext
中的Authentication
对象。只有AuthenticationManager
和AccessDecisionManager
成功处理了原始的Authentication
对象时才会发生这种情况。RunAsManager
将指示应该在SecurityInterceptorCallback
期间使用的替换Authentication
对象(如果有的话)。
通过在安全对象回调阶段临时替换Authentication
对象,安全调用将能够调用其他需要不同身份验证和授权凭证的对象。它还能够对特定的GrantedAuthority
对象执行任何内部安全检查。因为Spring Security提供了许多helper类,它们基于SecurityContextHolder
的内容自动配置远程协议,所以这些run-as
替换在调用远程web服务时特别有用
处理logout
这部分参考我的另一篇文章
https://console.blog.csdn.net/article/details/116464373