From 35d39bcea00c721c67d22e3cd75a453eb90f6d68 Mon Sep 17 00:00:00 2001 From: asahi Date: Fri, 15 Mar 2024 23:14:26 +0800 Subject: [PATCH] =?UTF-8?q?=E9=98=85=E8=AF=BBspring=20security=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spring/Spring Security/Spring Security.md | 119 ++++++++++++++-------- 1 file changed, 75 insertions(+), 44 deletions(-) diff --git a/spring/Spring Security/Spring Security.md b/spring/Spring Security/Spring Security.md index a35b548..375f77a 100644 --- a/spring/Spring Security/Spring Security.md +++ b/spring/Spring Security/Spring Security.md @@ -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中包含有SecurityContextHolder,SecurityContextHolder负责将当前线程和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是相当安全的,如果想要在该已认证主体的请求被处理完成之后清除SecurityContext,Spring 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 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,在许多场景下凭据会在用户认证成功之后被清空,为了保证凭据不会被泄露 -- 权限(authorities):该GrantedAuthority集合是用户被授予的高层次许可。许可通常是用户角色或者作用域范围。 +- 凭据(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