0


【8】Spring Boot 3 集成组件:安全组件 spring security【官网概念篇】

目录

个人主页: 【⭐️个人主页】
需要您的【💖 点赞+关注】支持 💯


【8】Spring Boot 3 集成组件:安全组件 spring security

📖 本文核心知识点:

  • spring security
  • B

官网Doc : Spring Security


Spring Security 简介

Spring Security

是一个框架,它提供

身份验证

授权

和针对常见

攻击的保护

。它具有保护命令式和响应式应用程序的一流支持,是保护基于spring的应用程序的事实上的标准。

先决条件

Spring Security

需要

Java 8

更高版本

运行时环境

由于Spring Security旨在以

自包含的方式

进行操作,因此不需要在Java运行时环境中放置任何特殊的配置文件。特别是,您不需要配置特殊的Java身份验证和授权服务(JAAS)策略文件,也不需要将Spring Security放入公共类路径位置。

引入依赖

Maven

<dependencies><!-- ... other dependency elements ... --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency></dependencies>

Gradle

implementation "org.springframework.boot:spring-boot-starter-security"

身份验证

密码存储

Spring Security的PasswordEncoder接口用于执行密码的单向转换,以便安全地存储密码。假设PasswordEncoder是单向转换,那么当密码转换需要是双向的(例如存储用于向数据库进行身份验证的凭据)时,它就没有用了。通常,PasswordEncoder用于存储需要在身份验证时与用户提供的密码进行比较的密码。

密码存储历史

  • 密码以明文形式存储> 多年来,存储密码的标准机制已经发生了变化。一开始,密码以明文形式存储。密码被认为是安全的,因为数据存储将密码保存在访问它所需的凭据中。然而,恶意用户能够通过使用SQL注入等攻击找到大量用户名和密码的“数据转储”。随着越来越多的用户凭证公开,安全专家意识到我们需要采取更多措施来保护用户的密码。
  • 单向散列(例如SHA-256)运行密码后存储密码> 当用户尝试进行身份验证时,将把散列密码与他们键入的密码的散列进行比较。这意味着系统只需要存储密码的单向散列。如果发生泄露,则只暴露密码的单向散列。由于哈希值是单向的,并且在计算上很难猜测给定哈希值的密码,因此不值得花费精力去找出系统中的每个密码。为了破解这个新系统,恶意用户决定创建名为“> > 彩虹表> > ”的查找表。他们不是每次都猜测每个密码,而是计算一次密码并将其存储在查找表中。
  • 为了降低彩虹表的有效性,开发者被鼓励使用盐渍密码。> 不是只使用密码作为哈希函数的输入,而是为每个用户的密码生成随机字节(称为salt)。盐和用户的密码将通过散列函数运行,以产生唯一的散列。盐将以明文形式与用户密码一起存储。然后,当用户尝试进行身份验证时,将把哈希后的密码与存储盐的哈希值和用户键入的密码进行比较。独特的盐意味着彩虹表不再有效,因为每个盐和密码组合的哈希值都不同。> 在现代,我们意识到> > 加密散列(如SHA-256)不再安全> > 。原因在于,使用现代硬件,我们每秒可以执行数十亿次哈希计算。这意味着我们可以轻松地单独破解每个密码。
  • 现在鼓励开发人员利用自适应单向函数来存储密码。> 使用自适应单向函数对密码进行验证是有意的资源密集型操作(它们有意使用大量CPU、内存或其他资源)。一个自适应单向功能允许配置一个“工作因素”,可以随着硬件变得更好而增长。我们建议将“工作因子”调整为大约一秒钟来验证系统上的密码。这种权衡是为了使攻击者难以破解密码,但代价不会太高,不会给您自己的系统带来过多的负担或激怒用户。Spring Security试图为“工作因素”提供一个良好的起点,但是我们鼓励用户为他们自己的系统定制“工作因素”,因为不同系统的性能差异很大。> > 应该使用的自适应单向函数的示例包括bcrypt、PBKDF2、scrypt和argon2> > 。> 由于自适应单向函数故意占用大量资源,因此为每个请求验证用户名和密码会显著降低应用程序的性能。Spring Security(或任何其他库)无法加快密码的验证速度,因为安全性是通过使验证资源密集来获得的
  • 鼓励用户将长期凭证(即用户名和密码)交换为短期凭证(如会话和OAuth令牌等)。> > 短期凭证> > 可以> > 快速验证> > 而不会损失任何安全性。

DelegatingPasswordEncoder

Spring Security 5.0之前

,默认的

PasswordEncoder

NoOpPasswordEncoder

,它需要

明文密码

。根据Password History部分,您可能期望默认的PasswordEncoder现在是类似于

BCryptPasswordEncoder

的东西。然而,这忽略了现实世界中的三个问题:

  1. 许多应用程序使用不容易迁移的旧密码编码。
  2. 密码存储的最佳实践将再次更改。
  3. 作为一个框架,Spring Security不能频繁地进行破坏性更改。

相反,Spring Security引入了

DelegatingPasswordEncoder

,它通过以下方式解决了所有问题:

  • 确保使用当前密码存储建议对密码进行编码
  • 允许以现代和遗留格式验证密码
  • 允许将来升级编码

你可以使用

PasswordEncoderFactories

很容易地构造一个

DelegatingPasswordEncoder

的实例:

PasswordEncoder passwordEncoder =PasswordEncoderFactories.createDelegatingPasswordEncoder();

创建自定义DelegatingPasswordEncoder

String idForEncode ="bcrypt";Map encoders =newHashMap<>();
encoders.put(idForEncode,newBCryptPasswordEncoder());
encoders.put("noop",NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2",Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());
encoders.put("pbkdf2@SpringSecurity_v5_8",Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("scrypt",SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());
encoders.put("scrypt@SpringSecurity_v5_8",SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("argon2",Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
encoders.put("argon2@SpringSecurity_v5_8",Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("sha256",newStandardPasswordEncoder());PasswordEncoder passwordEncoder =newDelegatingPasswordEncoder(idForEncode, encoders);

密码存储格式

密码的一般格式为:

DelegatingPasswordEncoder

存储格式

{id}encodedPassword
# 例子
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG 
{noop}password 
{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc 
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=  
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0 
id

是一个标识符,用于查找应该使用哪个

PasswordEncoder

, encodedPassword是所选PasswordEncoder的原始编码密码。

id

必须位于密码的开头,以

{开始,以}

结束。如果找不到id,则将id设置为空。例如,下面可能是使用不同id值编码的密码列表。所有的原始密码都是密码。

密码加解密类

  • BCryptPasswordEncoder
  • Argon2PasswordEncoder
  • Pbkdf2PasswordEncoder
  • SCryptPasswordEncoder
// Create an encoder with strength 16BCryptPasswordEncoder encoder =newBCryptPasswordEncoder(16);String result = encoder.encode("myPassword");assertTrue(encoder.matches("myPassword", result));// Create an encoder with all the defaultsArgon2PasswordEncoder encoder =Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();String result = encoder.encode("myPassword");assertTrue(encoder.matches("myPassword", result));// Create an encoder with all the defaultsPbkdf2PasswordEncoder encoder =Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8();String result = encoder.encode("myPassword");assertTrue(encoder.matches("myPassword", result));// Create an encoder with all the defaultsSCryptPasswordEncoder encoder =SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8();String result = encoder.encode("myPassword");assertTrue(encoder.matches("myPassword", result));

自定义密码存储

Spring Security默认使用

DelegatingPasswordEncoder

。但是,您可以通过将

PasswordEncoder

公开为

Spring bean

来定制它。

如果您正在从Spring Security 4.2迁移。通过公开NoOpPasswordEncoder bean,您可以恢复到以前的行为。

@BeanpublicstaticNoOpPasswordEncoderpasswordEncoder(){returnNoOpPasswordEncoder.getInstance();}

体系结构 Architecture

本节讨论基于Servlet的应用程序中的Spring Security高级体系结构。我们将在参考文献的身份验证、授权和防止漏洞利用保护部分中建立这种高层次的理解。

Servlet 过滤器

Spring Security 的 Servlet 支持基于

Servlet 过滤器

,因此首先了解过滤器的作用会很有帮助。 下图显示了单个 HTTP 请求的处理程序的典型分层。

FilterChain
Figure 1. FilterChain

客户端向应用程序发送一个请求,容器创建一个

 FilterChain

,其中包含

Filter 

实例和

Servlet

,应该根据请求URI的路径来处理

HttpServletRequest

。在Spring MVC应用程序中,

Servlet

DispatcherServlet 

的一个实例。一个

Servlet

最多可以处理一个

HttpServletRequest 

HttpServletResponse

。然而,可以使用多个

Filter

来完成如下工作。

防止下游的 Filter 实例或 Servlet 被调用。在这种情况下,Filter 通常会使用 HttpServletResponse 对客户端写入响应。

修改下游的 Filter 实例和 Servlet 所使用的 HttpServletRequest 或 HttpServletResponse。

过滤器的执行顺序来自于传入它的 FilterChain。

publicvoiddoFilter(ServletRequest request,ServletResponse response,FilterChain chain){// do something before the rest of the application
    chain.doFilter(request, response);// invoke the rest of the application// do something after the rest of the application}

由于一个

 Filter

只影响下游的

 Filter 实例

Servlet

,所以每个 Filter 的

调用顺序

是非常重要的。

完整调用链类图

在这里插入图片描述

DelegatingFilterProxy

Spring 提供了一个名为

DelegatingFilterProxy 

Filter 实现

,允许在

Servlet 容器的生命周期

Spring 的 ApplicationContext 之间建立桥梁

。Servlet容器允许通过使用自己的标准来注册 Filter 实例,但它不知道 Spring 定义的 Bean。你可以通过标准的Servlet容器机制来注册 DelegatingFilterProxy,但将

所有工作委托给实现 Filter 的Spring Bean

在这里插入图片描述

DelegatingFilterProxy

ApplicationContext

查找

Bean Filter0

,然后调用

Bean Filter0

。下面的列表显示了 DelegatingFilterProxy 的伪代码。

publicvoiddoFilter(ServletRequest request,ServletResponse response,FilterChain chain){Filter delegate =getFilterBean(someBeanName); 
    delegate.doFilter(request, response);}
  1. 延迟地获取被注册为Spring Bean的 Filter。 对于 DelegatingFilterProxy 中的例子,delegate 是 Bean Filter0 的一个实例。
  2. 将工作委托给 Spring Bean。

🅰️

DelegatingFilterProxy

的另一个

好处

是,它允许延迟查找 Filter Bean实例。
这一点很重要,因为在容器启动之前,容器需要注册 Filter 实例。然而, Spring 通常使用 ContextLoaderListener 来加载 Spring Bean,这在需要注册 Filter 实例之后才会完成。

FilterChainProxy

Spring Security 的 Servlet 支持包含在

FilterChainProxy

中。FilterChainProxy 是 Spring Security 提供的一个特殊的 Filter,允许通过

 SecurityFilterChain

委托给许多

 Filter 实例

。由于

 FilterChainProxy

是一个Bean,它通常被包裹在

DelegatingFilterProxy 

中。
在这里插入图片描述
FilterChainProxy 有很多优势。首先,它为 Spring Security 的所有 Servlet 支持提供了一个起点。由于这个原因,如果你试图对 Spring Security 的 Servlet 支持进行故障诊断,在 FilterChainProxy 中添加一个调试点是一个很好的开始。

其次,由于 FilterChainProxy 是 Spring Security 使用的核心,它可以执行一些不被视为可有可无的任务。 例如,它清除了 SecurityContext 以避免内存泄漏。它还应用Spring Security的 HttpFirewall 来保护应用程序免受某些类型的攻击。

此外,它在确定何时应该调用 SecurityFilterChain 方面提供了更大的灵活性。在Servlet容器中,Filter 实例仅基于URL被调用。 然而,FilterChainProxy 可以通过使用 RequestMatcher 接口,根据 HttpServletRequest 中的任何内容确定调用。

SecurityFilterChain

SecurityFilterChain 被 FilterChainProxy 用来确定当前请求应该调用哪些 Spring Security Filter 实例。
在这里插入图片描述
在 Multiple SecurityFilterChain 图中, FilterChainProxy 决定应该使用哪个 SecurityFilterChain。只有第一个匹配的 SecurityFilterChain 被调用。如果请求的URL是 /api/messages/,它首先与 /api/** 的 SecurityFilterChain0 模式匹配,所以只有 SecurityFilterChain0 被调用,尽管它也与 SecurityFilterChainn 匹配。如果请求的URL是 /messages/,它与 /api/** 的 SecurityFilterChain0 模式不匹配,所以 FilterChainProxy 继续尝试每个 SecurityFilterChain。假设没有其他 SecurityFilterChain 实例相匹配,则调用 SecurityFilterChainn。

请注意,SecurityFilterChain0 只配置了三个 security Filter 实例。然而,SecurityFilterChainn 却配置了四个 security Filter 实例。值得注意的是,每个 SecurityFilterChain 都可以是唯一的,并且可以单独配置。事实上,如果应用程序希望 Spring Security 忽略某些请求,那么一个 SecurityFilterChain 可能会有零个 security Filter 实例。

Security Filter

Security Filter 是通过 SecurityFilterChain API 插入 FilterChainProxy 中的。

这些 filter 可以用于许多不同的目的,如

 认证、 授权、 漏洞保护

等等。filter 是按照特定的顺序执行的,以保证它们在正确的时间被调用,例如,执行认证的 Filter 应该在执行授权的 Filter 之前被调用。一般来说,没有必要知道 Spring Security 的 Filter 的顺序。但是,有些时候知道顺序是有好处的,如果你想知道它们,可以查看 FilterOrderRegistration 代码。

打印出 Security Filter

org.springframework.security.web.session.DisableEncodeUrlFilter@404db674,
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@50f097b5,
org.springframework.security.web.context.SecurityContextHolderFilter@6fc6deb7,
org.springframework.security.web.header.HeaderWriterFilter@6f76c2cc,
org.springframework.security.web.csrf.CsrfFilter@c29fe36,
org.springframework.security.web.authentication.logout.LogoutFilter@ef60710,
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@7c2dfa2,
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@4397a639,
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@7add838c,
org.springframework.security.web.authentication.www.BasicAuthenticationFilter@5cc9d3d0,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7da39774,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@32b0876c,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@3662bdff,
org.springframework.security.web.access.ExceptionTranslationFilter@77681ce4,
org.springframework.security.web.access.intercept.AuthorizationFilter@169268a7]

添加自定义 Filter 到 Filter Chain

  1. 定义一个Filter > 集成 > > Filter> > ,or > > OncePerRequestFilter>
  2. 添加到 SecurityFilterChain
@BeanSecurityFilterChainfilterChain(HttpSecurity http)throwsException{
    http
        // ....addFilterBefore(newTenantFilter(),AuthorizationFilter.class);return http.build();}

🅰️ 注意点: 多次调用Filter问题

当你把你的 filter 声明为 Spring Bean 时要小心,可以用 @Component 注解它,也可以在配置中把它声明为 Bean,因为 Spring Boot 会自动 在嵌入式容器中注册它。这可能会导致 filter 被调用两次,一次由容器调用,一次由 Spring Security 调用,而且顺序不同。

如果你仍然想把你的 filter 声明为 Spring Bean,以利用依赖注入,避免重复调用,你可以通过声明 FilterRegistrationBean Bean 并将其 enabled 属性设置为 false 来告诉 Spring Boot 不要向容器注册它:

@BeanpublicFilterRegistrationBean<TenantFilter>tenantFilterRegistration(TenantFilter filter){FilterRegistrationBean<TenantFilter> registration =newFilterRegistrationBean<>(filter);
    registration.setEnabled(false);return registration;}

处理 Security 异常

在这里插入图片描述

  1. 首先,ExceptionTranslationFilter 调用 FilterChain.doFilter(request, response) 来调用应用程序的其他部分。
  2. 如果用户没有被认证,或者是一个 AuthenticationException,那么就 开始认证。- SecurityContextHolder 被清理掉。- HttpServletRequest 被保存起来,这样一旦认证成功,它就可以用来重放原始请求。- AuthenticationEntryPoint 用于请求客户的凭证。例如,它可以重定向到一个登录页面或发送一个 WWW-Authenticate 头。
  3. 否则,如果是 AccessDeniedException,那么就是 Access Denied。 AccessDeniedHandler 被调用来处理拒绝访问(access denied)。

🅰️ 注意点:

如果应用程序没有抛出

AccessDeniedException

 AuthenticationException

,那么

ExceptionTranslationFilter 

就不会做任何事情

保存认证之间的请求

如在 处理

Security 异常

中所说明的,当一个请求没有认证,并且是针对需要认证的资源时,有必要保存认证资源的请求,以便在认证成功后重新请求。在Spring Security中,这是通过使用

RequestCache

实现来保存

HttpServletRequest

的。

RequestCache

HttpServletRequest 被保存在 RequestCache。当用户成功认证后,RequestCache 被用来重放原始请求。RequestCacheAwareFilter 就是使用 RequestCache 来保存 HttpServletRequest 的。

默认情况

下,使用一个

HttpSessionRequestCache

。下面的代码演示了如何定制 RequestCache 的实现,如果名为 continue 的参数存在,它将用于检查 HttpSession 是否有保存的请求。

@BeanDefaultSecurityFilterChainspringSecurity(HttpSecurity http)throwsException{HttpSessionRequestCache requestCache =newHttpSessionRequestCache();
    requestCache.setMatchingRequestParameterName("continue");
    http
        // ....requestCache((cache)-> cache
            .requestCache(requestCache));return http.build();}
防止请求被保存

有很多原因,你可能想不在 session 中存储用户的未经认证的请求。你可能想把这种存储卸载到用户的浏览器上,或者把它存储在数据库中。或者你可能想关闭这个功能,因为你总是想把用户重定向到主页,而不是他们登录前试图访问的页面。

要做到这一点,你可以使用

NullRequestCache

实现.

@BeanSecurityFilterChainspringSecurity(HttpSecurity http)throwsException{RequestCache nullRequestCache =newNullRequestCache();
    http
        // ....requestCache((cache)-> cache
            .requestCache(nullRequestCache));return http.build();}

认证

Servlet 认证架构

  • SecurityContextHolder - SecurityContextHolder 是 Spring Security 存储 认证 用户细节的地方。
  • SecurityContext - 是从 SecurityContextHolder 获得的,包含了当前认证用户的 Authentication (认证)。
  • Authentication - 可以是 AuthenticationManager 的输入,以提供用户提供的认证凭证或来自 SecurityContext的当前用户。
  • GrantedAuthority - 在 Authentication (认证)中授予委托人的一种权限(即role、scope等)。
  • AuthenticationManager - 定义 Spring Security 的 Filter 如何执行 认证 的API。
  • ProviderManager - 最常见的 AuthenticationManager 的实现。
  • AuthenticationProvider - 由 ProviderManager 用于执行特定类型的认证。
  • 用 AuthenticationEntryPoint 请求凭证 - 用于从客户端请求凭证(即重定向到登录页面,发送 WWW-Authenticate 响应,等等)。
  • AbstractAuthenticationProcessingFilter - 一个用于认证的基本 Filter。这也让我们很好地了解了认证的高层流程以及各部分是如何协作的。

认证官网学习:https://springdoc.cn/spring-security/servlet/authentication/architecture.html

SecurityContextHolder

在这里插入图片描述

SecurityContextHolder 是 Spring Security 存储用户 验证 细节的地方。Spring Security 并不关心 SecurityContextHolder 是如何被填充的。如果它包含一个值,它就被用作当前认证的用户。
默认情况下,SecurityContextHolder 使用 ThreadLocal 来存储这些细节,这意味着 SecurityContext 对同一线程中的方法总是可用的,即使 SecurityContext 没有被明确地作为参数传递给这些方法。如果你注意在处理完当前委托人的请求后清除该线程,以这种方式使用 ThreadLocal 是相当安全的。Spring Security 的 FilterChainProxy 确保 SecurityContext 总是被清空。

SecurityContext

SecurityContext 是从 SecurityContextHolder 中获得的。SecurityContext 包含一个 Authentication 对象。

Authentication

认证是一个用户在当前系统下,存在其身份后的 身份凭证,一般是 该身份的 名称和权限信息填充

认证(Authentication)包含了:

  • principal: 识别用户。当用用户名/密码进行认证时,这通常是 UserDetails 的一个实例。
  • credentials: 通常是一个密码。在许多情况下,这在用户被认证后被清除,以确保它不会被泄露。
  • authorities: GrantedAuthority 实例是用户被授予的高级权限。两个例子是角色(role)和作用域(scope)。

AuthenticationManager

这是一个接口定义。其具体实现·ProviderManager·提供。

AuthenticationManager 是定义 Spring Security 的 Filter 如何执行 认证 的API。返回的 认证是由调用 AuthenticationManager 的控制器(即 Spring Security的 Filter 实例)在 SecurityContextHolder 上设置的。如果你不与 Spring Security 的 Filter 实例集成,你可以直接设置 SecurityContextHolder,不需要使用 AuthenticationManager。

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

ProviderManager

在这里插入图片描述

  1. ProviderManager是最常用的AuthenticationManager的实现。
  2. ProviderManager 委托给一个 ListAuthenticationProvider实例。> 每个 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 属性。参见 ProviderManager 类的Javadoc。

ProviderManager 也不是具体执行认证的类。ProviderManager 将具体认证职责,委托给内部的 ListAuthenticationProvider 列表遍历。
并且保证,只有前一个认证通过即可,后一个认证不通过,下一个继续匹配支持的认证进行认证操作

AuthenticationProvider

你可以在 ProviderManager 中注入多个 AuthenticationProvider 实例。每个 AuthenticationProvider 都执行一种特定类型的认证。例如,

DaoAuthenticationProvider

支持基于用户名/密码的认证,而

JwtAuthenticationProvider 

支持认证JWT令牌。
在这里插入图片描述

用 AuthenticationEntryPoint 请求凭证

AuthenticationEntryPoint 用于发送一个要求客户端提供凭证的HTTP响应。

简而言之,就是告诉 客户端,去进行登录操作

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

在其他情况下,客户端向他们未被授权访问的资源发出未经认证的请求。在这种情况下, AuthenticationEntryPoint 的实现被用来请求客户端的凭证。 AuthenticationEntryPoint 的实现可能会执行 重定向到一个登录页面,用 WWW-Authenticate 头来响应,或采取其他行动。

🅰️ AbstractAuthenticationProcessingFilter

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

AbstractAuthenticationProcessingFilter 可以对提交给它的任何认证请求进行认证。

在这里插入图片描述
在这里插入图片描述

UsernamePasswordAuthenticationFilter

UsernamePasswordAuthenticationFilter 是 AbstractAuthenticationProcessingFilter的实现类。

所以流程与 AbstractAuthenticationProcessingFilter的流程一致

在这里插入图片描述

🅱️DaoAuthenticationProvider

承接上节

AuthenticationManager

认证流程
在这里插入图片描述
number 1 读取用户名和密码部分的认证

Filter

UsernamePasswordAuthenticationToken

传递给

AuthenticationManager

,它由 ProviderManager 实现。

number 2

ProviderManager

被配置为使用一个

DaoAuthenticationProvider

类型的 AuthenticationProvider。

number 3

DaoAuthenticationProvider

UserDetailsService

中查找

UserDetails

number 4

DaoAuthenticationProvider

使用 PasswordEncoder 来验证上一步返回的

UserDetails

上的密码。

number 5 当认证成功时,返回的 Authentication 是

UsernamePasswordAuthenticationToken

类型,并且有一个委托人(principal)是由配置的

UserDetailsService

返回的

UserDetails

。最终,返回的

UsernamePasswordAuthenticationToken

被认证 Filter 设置在 SecurityContextHolder 上。

UserDetailsService

UserDetailsService 被 DaoAuthenticationProvider 用来检索用户名、密码和其他属性,以便用用户名和密码进行认证。Spring Security提供了 UserDetailsService 的 内存 和 JDBC 实现。

你可以通过暴露一个自定义的 UserDetailsService 作为一个bean来定义自定义认证。例如,假设 CustomUserDetailsService 实现了 UserDetailsService,那么下面的列表将自定义认证。

@BeanCustomUserDetailsServicecustomUserDetailsService(){returnnewCustomUserDetailsService();}

UserDetails

UserDetails 由 UserDetailsService 返回。DaoAuthenticationProvider 验证 UserDetails,然后返回一个 Authentication,该 Authentication 的委托人(principal)是由配置的 UserDetailsService 返回的。

PasswordEncoder

Spring Security的servlet支持包括通过与 PasswordEncoder 集成来安全地存储密码。你可以通过 暴露一个 PasswordEncoder Bean 来定制Spring Security使用的 PasswordEncoder 实现。

处理登出 Logout

当你包含 spring-boot-starter-security 依赖或使用 @EnableWebSecurity 注解时,Spring Security 将添加其注销支持,并默认响应 GET /logout 和 POST /logout。

如果你请求 GET /logout,那么 Spring Security 会显示一个注销确认页面。除了为用户提供一个有价值的双重检查机制外,它还提供了一个简单的方法来为 POST /logout 提供 所需的 CSRF token。
如果你请求 POST /logout,那么它将使用一系列 LogoutHandler 执行以下默认操作:

  • 使 HTTP session无效 (SecurityContextLogoutHandler)。
  • 清理 SecurityContextHolderStrategy (SecurityContextLogoutHandler)。
  • 清理 SecurityContextRepository (SecurityContextLogoutHandler)。
  • 清理任何 RememberMe 认证 (TokenRememberMeServices / PersistentTokenRememberMeServices)。
  • 清除任何已保存的 CSRF token (CsrfLogoutHandler)。
  • 触发 LogoutSuccessEvent (LogoutSuccessEventPublishingLogoutHandler)

一旦完成,那么它将行使其默认的 LogoutSuccessHandler,它重定向到 /login?logout。

如果你使用Java配置,你可以通过调用 logout DSL 中的 addLogoutHandler 方法来添加自己的清理动作,像这样:

CookieClearingLogoutHandler cookies =newCookieClearingLogoutHandler("our-custom-cookie");
http
    .logout((logout)-> logout.addLogoutHandler(cookies))

使用 Clear-Site-Data 来注销用户
Clear-Site-Data HTTP header 是浏览器支持的一个指令,用于清除属于自己网站的cookie、storage和缓存。这是一个方便和安全的方法,以确保所有的东西,包括会话cookie,在注销时都被清理掉了。

HeaderWriterLogoutHandler clearSiteData =newHeaderWriterLogoutHandler(newClearSiteDataHeaderWriter());
http
    .logout((logout)-> logout.addLogoutHandler(clearSiteData))

授权

默认情况下,AuthorizationFilter 是最后一个

默认情况下,

AuthorizationFilter 

是 Spring

Security filter chain

中的

最后

一个。这意味着 Spring Security 的 authentication filter、漏洞保护 和其他 filter 集成都

不需要

授权。如果你在 AuthorizationFilter 之前添加自己的 filter,它们也将不需要授权;否则,它们将需要授权。

这一点通常在你添加 Spring MVC 端点时变得很重要。因为它们是由

DispatcherServlet

执行的,而这是在

AuthorizationFilter

之后,你的端点需要 包含在 authorizeHttpRequests 中才能被允许。

在这里插入图片描述

  1. 首先,AuthorizationFilter 构造一个 Supplier,从 SecurityContextHolder 中检索一个 Authentication。
  2. 其次,它将 Supplier 和 HttpServletRequest 传递给 AuthorizationManager。AuthorizationManager 将请求与 authorizeHttpRequests 中的模式相匹配,并运行相应的规则。
  3. 如果授权被拒绝,会发布一个 AuthorizationDeniedEvent,并抛出一个 AccessDeniedException。在这种情况下,ExceptionTranslationFilter 会处理 AccessDeniedException。
  4. 如果访问被授权,就会 发布一个 AuthorizationGrantedEvent,AuthorizationFilter 继续进行 FilterChain,允许应用程序正常处理。

授权架构

重点查看 架构 章节,讲述了授权的相关类和图解

授权架构

AuthorizationManager

AuthorizationManager
3.0后,

AuthorizationManager

同时取代了 AccessDecisionManager 和 AccessDecisionVoter。

AuthorizationManager 被 Spring Security 的 基于请求、 基于方法 和 基于消息 的授权组件所调用,并负责做出最终的访问控制决定。AuthorizationManager 接口包含两个方法:

AuthorizationDecisioncheck(Supplier<Authentication> authentication,Object secureObject);defaultAuthorizationDecisionverify(Supplier<Authentication> authentication,Object secureObject)throwsAccessDeniedException{// ...}

AuthorizationManager 的检查方法被传递给它所需要的所有相关信息,以便做出授权决定。特别是,传递安全对象(secure Object)使那些包含在实际安全对象调用中的参数能够被检查到。例如,让我们假设安全对象是一个 MethodInvocation。查询 MethodInvocation 的任何客户参数是很容易的,然后在 AuthorizationManager 中实现某种安全逻辑以确保委托人(principal)被允许对该客户进行操作。如果访问被授予,实现应返回 “negative” 的 AuthorizationDecision,如果访问被拒绝,应返回 “negative” 的 AuthorizationDecision,如果不作出决定,则返回空的 AuthorizationDecision。

verify 调用 check,然后在出现 “negative” 的 AuthorizationDecision 决定时抛出一个 AccessDeniedException。
AuthorizationManager 来控制授权的所有方面(aspect),但Spring Security提供了一个委托的 AuthorizationManager,可以与个别的 AuthorizationManager 协作。

基于授权的AuthorizationManager实现

RequestMatcherDelegatingAuthorizationManager 将把请求与最合适的委托(delegate) AuthorizationManager 相匹配。 对于方法安全,你可以使用

AuthorizationManagerBeforeMethodInterceptor

AuthorizationManagerAfterMethodInterceptor

Authorization Manager Implementations 说明了相关的类。

在这里插入图片描述

自定义授权管理器(Authorization Manager)

很明显,你也可以实现一个自定义的

 AuthorizationManager

,你可以把你想要的任何

访问控制逻辑

放在里面。

使用路径(Path)参数

http
    .authorizeHttpRequests((authorize)-> authorize
        .requestMatchers("/resource/{name}").access(newWebExpressionAuthorizationManager("#name == authentication.name")).anyRequest().authenticated())

使用自定义的 Matcher

在 Java 配置中,你可以创建自己的 RequestMatcher 并像这样提供给 DSL:

RequestMatcher printview =(request)-> request.getParameter("print")!=null;
http
    .authorizeHttpRequests((authorize)-> authorize
        .requestMatchers(printview).hasAuthority("print").anyRequest().authenticated())

因为 RequestMatcher 是一个

函数接口

,你可以在DSL中作为一个

lambda 

提供。然而,如果你想从请求中提取值,你需要有一个具体的类,因为这需要覆盖一个 default 方法。

授权请求

简单的总结,以下是DSL内置的授权规则:

permitAll - 该请求不需要授权,是一个公共端点;请注意,在这种情况下,永远不会从 session 中检索 Authentication。

denyAll - 该请求在任何情况下都是不允许的;注意在这种情况下,永远不会从 session 中检索 Authentication。

hasAuthority - 该请求要求 Authentication 有一个符合给定值的 GrantedAuthority。

hasRole - hasAuthority 的一个快捷方式,其前缀为 ROLE_ 或任何被配置为默认的前缀。

hasAnyAuthority - 该请求要求 Authentication 有一个符合任何给定值的 GrantedAuthority。

hasAnyRole - hasAnyAuthority 的一个快捷方式,其前缀为 ROLE_ 或任何被配置为默认的前缀。

access - 该请求使用这个自定义的 AuthorizationManager 来确定访问权限。


本文转载自: https://blog.csdn.net/k316378085/article/details/134462519
版权归原作者 无难事者若执 所有, 如有侵权,请联系我们删除。

“【8】Spring Boot 3 集成组件:安全组件 spring security【官网概念篇】”的评论:

还没有评论