阅读spring security文档

This commit is contained in:
asahi
2024-03-15 23:14:26 +08:00
parent 6c0404485c
commit 35d39bcea0

View File

@@ -10,7 +10,7 @@
- [处理Security异常](#处理security异常)
- [在多次认证之间保存request](#在多次认证之间保存request)
- [RequestCache](#requestcache)
- [Spring Security身份认证的结](#spring-security身份认证的结)
- [Spring Security身份认证](#spring-security身份认证)
- [SecurityContextHolder](#securitycontextholder)
- [SecurityContext](#securitycontext)
- [Authentication](#authentication)
@@ -20,7 +20,6 @@
- [AuthenticationProvider](#authenticationprovider)
- [AuthenticationEntryPoint](#authenticationentrypoint)
- [AbstractAuthenticationProcessingFilter](#abstractauthenticationprocessingfilter)
- [认证流程](#认证流程)
- [用户名/密码认证](#用户名密码认证)
- [Form Login](#form-login)
- [用户被重定向到登录页面的过程](#用户被重定向到登录页面的过程)
@@ -204,76 +203,108 @@ SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
}
```
## Spring Security身份认证的结
## Spring Security身份认证
### SecurityContextHolder
Spring Security身份认证的核心模型SecurityContextHolder包含有SecurityContext。
> Spring Security将被认证用户的详细信息details存储在SecurityContextHolder中。Spring Security不在乎该SecurityContextHolder是如何被填充的只要该SecurityContextHolder有值那么其将会被用作当前已经被认证的用户。
SecurityContextHolder用于存储用户的认证信息Spring Security身份认证认证模型的核心SecurityContextHolder包含有SecurityContextHolderSecurityContextHolder负责将当前线程和SecurityContext相关联
> ***将用户标识为已认证最简单的方法是为该用户设置SecurityContextHolder。***
如果想要将用户表示为已经通过身份认证最简单的方式是向SecurityContextHolder中设置值,示例如下:
```java
// 设置SecurityContextHolder
SecurityContext context = SecurityContextHolder.createEmptyContext();
SecurityContext context = SecurityContextHolder.createEmptyContext(); // 1
Authentication authentication =
new TestingAuthenticationToken("username", "password", "ROLE_USER");
new TestingAuthenticationToken("username", "password", "ROLE_USER"); //2
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
SecurityContextHolder.setContext(context); //3
```
默认情况下SecurityContextHolder通过ThreadLocal来存储SecurityContext故而SecurityContext对于位于同一线程之下的方法来说都可以访问。
> 使用ThreadLocal来存储SecurityContext是相当安全的如果想要在该已认证主体的请求被处理完成之后清除SecurityContextSpring Security中的FilterChainProxy会保证该SecurityContext被清除。
上述流程如下所示:
1. 创建了一个空的SecurityContext。在最开始应该通过createEmptyContext方法创建一个空的SecurityContext而不是使用`getContext().setAuthentication(authentication)`,从而避免在多线程下的竞争场景
2. 第二步中创建了一个Authentication对象Spring Security不关注向SecurityContext中塞入了哪种类型的Authentication实现
3. 在第三步中将SecurityContext设置到了SecurityContextHolder中spring使用设置的SecurityContext信息进行身份认证
如果后续操作想要访问当前已认证用户的信息可以访问SecurityContextHolder。
```java
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在同一线程内可以共享。在SecurityContextHolder中使用ThreadLocal是安全的不会发生当前请求完成而SecurityContext未被清理的情况Spring的FilterChainProxy保证SecurityContext会被清理。
在一些场景下可能不希望使用ThreadLocal来存储ContextHolder例如在swing程序下可能希望整个java虚拟机中的线程都共享同一个security context。可以在启动时为SecurityContextHolder指定一个strategy该strategy决定context应该如何被存储。
对于应用程序内所有线程共享的security context可以使用`SecurityContextHolder.MODE_GLOBAL`策略。
### SecurityContext
SecurityContext从SecurityContextHolder中获SecurityContext中含有Authentication对象
Security Context用于存储用户的身份认证信息,从SecurityContextHolder中获
### Authentication
Authentication在Spring Security中具有两个目的
- 作为AuthenticationManager的输入,用于提供待认证用户的认证凭证。当用于该场景下时isAuthenticated()方法返回值应该为false
- 代表当前已经认证过的用户。当前的Authentication可以从SecurityContext获取而默认情况下SecurityContext是存储在ThreadLocal中的
Authentication在Spring Security中具有两种使用场景
- 在用户尚未被认证的场景,向`AuthenticationManager`提供用于身份认证凭证
- 在用户已经认证的场景,Authentication用于代表当前已经通过认证的用户,此时可以通过SecurityContext获取用户认证信息
Authentication含有如下部分
Authentication含有如下信息
- 主体principal用于标识用户当通过username/password进行认证时其通常是UserDetails类的实例
- 凭据credentials通常是password在许多场景下凭据会在用户认证成功之后被清空,为了保证凭据不会被泄露
- 权限authoritiesGrantedAuthority集合是用户被授予的高层次许可。许可通常是用户角色或者作用域范围。
- 凭据credentials通常是password在许多场景下为了保证凭证不会被泄露,在用户通过认证后,凭证将会被清空
- 权限authorities代表用户被授予的权限,为`GrantedAuthority`的实例集合
### GrantedAuthority
GrantedAuthority是用户被授予的高层次许可,譬如用户角色或者作用域范围。
GrantedAuthority可以通过Authentication.getAuthorities()方法来获得该方法会返回一个GrantedAuthentication的集合。每个GrantedAuthentication都是一项被授予该用户的权限。
GrantedAuthority是用户被授予的权限。
可以通过`Authentication.getAuthorities()`方法来获取用户被授予的权限集合,该方法会返回`GrantedAuthority`的集合。
### AuthenticationManager
AuthenticationManager的API定义了Security Filters如何执行身份认证。对于身份认证返回的Authentication会被调用AuthenticationManager的controller设置到SecurityContextHolder中。
> AuthenticationManager的实现可以是任何类但是最通用的实现仍然是ProviderManager
AuthenticationManager定义了Security Filters如何执行认证。
AuthenticationManager会对传递给其的Authentication执行认证操作如果认证成功会返回一个被完全填充的Authetication对象。
在AuthenticationManager的authenticate方法返回Authentication对象之后调用AuthenticationManager的controller会将返回的Authentication对象设置到SecurityContextHolder中。
AuthenticationManager的实现可以是任何类最通用的实现类是`ProviderManager`.
### ProviderManager
ProviderManager是AuthenticationManager的最通用实现。ProviderManager将工作委托给一系列AuthenticationProvider。
> 对于每个ProviderManager都可以决定将该认证标识为成功、失败或者将认证工作委托给下游AuthenticationProvider。
> 如果所有的AuthenticationProvider都没有将该认证标识为成功或者失败那么整个认证流程失败并且抛出ProviderNotFoundException异常。
> ProviderNotFoundException是一个特殊的AuthenticationException该异常代表对传入Authentication的类型并没有配置该类型的ProviderManager
ProviderManager是最常用的AuthenticationManager实现类,ProviderMananger将认证工作委托给了一个`AuthenticationProvider`的实例集合。在集合中的每一个AuthenticationProvider都可以决定当前认证工作是认证成功、认证失败还是将认证工作交由集合中的后续AuthenticationProvider决定
> 在实践中,每个AuthenticationProvider知道如何处理一种特定类型的Authentication
如果AuthenticationProvider集合中没有任何AuthenticationProvider可以决定当前认证工作是成功或是失败那么当前认证工作会失败并且抛出`ProviderNotFoundException`异常。该异常代表当前的认证方式没有被支持。
默认情况下ProviderManager在认证请求成功后会尝试清除返回的Authentication对象中任何敏感的凭据信息这将会保证password等敏感信息保存时间尽可能地短减少泄露的风险
> 在实践中每个AuthenticationProvider都明确其应该负责的认证方式例如一个AuthenticationProvider负责用户名/密码形式的登录另一个AuthenticationProvider负责saml形式的登录。故而可以在支持多种认证方式的情况下只通过一个AuthenticationManager bean对象来进行整合多种认证方式
对于ProviderManager可以配置一个parent ProviderManager当本级没有AuthenticationProvider能够完成认证工作时将会将认证工作委托给父级manager。
默认情况下ProviderManager将会在认证成功后清空任任何成功登录后返回Authenticaiton对象的凭证信息。
### AuthenticationProvider
复数个AuthenticationProvider可以被注入到ProviderManager中每个AuthenticationProvider可以负责一种专门类型的认证例如DaoAuthenticationProvider负责username/password认证JwtAuthenticationProvider负责jwt token的认证
AuthenticationProvider负责执行特定类型的认证工作可以将多个AuthenticationProvider对象注入到同一个ProviderMananger中
例如DaoAuthenticationProvider支持基于用户名/密码的认证JwtAuthenticationManager则是支持基于jwt形式的认证。
### AuthenticationEntryPoint
AuthenticationEntryPoint用来发送一个Http Response用来向客户端请求认证凭据。
某些情况下客户端在请求资源时会主动在请求中包含凭据如username/password等。在这种情况下服务端并不需要再专门发送Http Response来向客户端请求认证凭据。
> AuthenticationEntryPoint用来向客户端请求认证凭据AuthenticationEntryPoint的实现可能会执行一个从定向操作将请求重定向到一个登录页面用于获取凭据并且返回一个WWW-Authentication Header
AuthenticationEntryPoint用于服务端向用户请求凭证(例如,重定向到登录页面,向客户端发送`WWW-Authenticate`响应等。)
一些场景下用户在请求资源时会主动发送在请求中包含凭据。在这些场景下spring security不用向客户返回相应来要求客户提供凭证
在其他场景下,客户端可能在未认证的情况下发送请求来访问资源,这时,可以使用`AuthenticationEntryPoint`的实现来向客户请求凭证。`AuthenticationEntryPoint`的实现可以重定向到登录页,或是向客户端发送`WWW-Authenticate`响应,或是采用其他方式来向客户端请求凭据。
### AbstractAuthenticationProcessingFilter
该类作为base filter用来对用户的凭据进行认证。再凭据被认证之前,Spring Security通会通过AuthenticationEntryPoint向客户请求认证凭据。
之后AbstractAuthenticationProcessingFilter会对提交的任何认证请求进行认证。
#### 认证流程
1. 当用户提交认证凭据之后AbstractAuthenticationProcesingFilter会根据HttpServletRequest对象创建一个Authentication对象用于认证,该Authentication的类型取决于AbstractAuthenticationProcessingFilter的子类类型。例如UsernamePasswordAuthenticationFilter会通过提交request中的username和password创建UsernamePasswordAuthenticationToken
2. 然后将构建产生的Authentication对象传入到AuthenticationManager中,进行认证
3. 如果认证失败,那么会失败SecurityContextHolder会被清空RememberMeService.logFail方法将会被调用AuthenticationFailureHandler也会被调用
4. 如果认证成功那么SessionAuthenticationStrategy将会收到登录的通知
5. 该Authentication对象再认证成功之后将会被设置到SecurityContextHolder中
6. RemeberMeService.logSuccess将会被调用
7. ApplicationEventPublisher发布InteractiveAuthenticationSuccessEvent.
8. AuthenticationSuccessHandler被调用
`AbstractAuthenticationProcessingFilter`用于认证用户的凭据。在凭据可以被认证之前,spring security通会通过`AuthentciationEntryPoint`向客户请求凭据。
AbstractAuthenticationProcessingFilter通过如下流程来执行认证
1. 当用户提交凭据时AbstractAuthenticationProcessingFilter会根据将要被认证的request创建一个Authentication对象,创建Authentication的类型于AbstractAuthenticationProcessingFilter的继承类。例如UsernamePasswordAuthenticationFilter将会基于request中提交的username和password创建一个UsernamePasswordAuthenticationToken
2. 接下来,Authentication将会被传递给AuthenticationMananger用于认证
3. 如果认证失败,那么
1. SecurityContextHolder将会被清空
2. RememberMeServices.loginFail方法将会被调用如果remeberMe没有被调用那么将不会执行任何操作
3. AuthenticationFailureHandler将会被调用
4. 如果认证成功:
1. 将会通知SessionAuthenticaitonStrategy将会被提示有一个新的登录
2. Authentication将会被设置到SecurityContextHolder中。如果想要在多个请求之前保存context需要手动调用`SecurityContextRepository#saveContext`方法调用后SecurityContext可以在后续的请求中自动被设置
3. 调用RememberMeServices.loginSuccess方法如果没有设置rememberMe那么不会执行任何操作
4. ApplicationEventPublisher将会发不InteractiveAuthenticationSuccessEvent事件
5. AuthenticationSuccessHandler将会被调用
## 用户名/密码认证
### Form Login