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

目录
  • 【8】Spring Boot 3 集成组件:安全组件 spring security
  • * Spring Security 简介
    
    • 先决条件
    • 引入依赖
    • 身份验证
    •   * 密码存储
      
      • 密码存储历史
      • DelegatingPasswordEncoder
      • 密码存储格式
      • 密码加解密类
      • 自定义密码存储
    • 体系结构 Architecture
    •   * Servlet 过滤器
      
      • DelegatingFilterProxy
      • FilterChainProxy
      • SecurityFilterChain
      • Security Filter
      • 添加自定义 Filter 到 Filter Chain
      • 处理 Security 异常
      • 保存认证之间的请求
      •     * RequestCache
        
        • 防止请求被保存
    • 认证
    •   * SecurityContextHolder
      
      • SecurityContext
      • Authentication
      • AuthenticationManager
      • ProviderManager
      • AuthenticationProvider
      • 用 AuthenticationEntryPoint 请求凭证
      • 🅰️ AbstractAuthenticationProcessingFilter
      • UsernamePasswordAuthenticationFilter
      • 🅱️DaoAuthenticationProvider
      • UserDetailsService
      • UserDetails
      • PasswordEncoder
      • 处理登出 Logout
    • 授权
    •   * 默认情况下,AuthorizationFilter 是最后一个
      
      • 授权架构
      • AuthorizationManager
      • 基于授权的AuthorizationManager实现
      • 自定义授权管理器(Authorization Manager)
      • 使用路径(Path)参数
      • 使用自定义的 Matcher
      • 授权请求

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


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

📖 本文核心知识点:

  • spring security
  • B

[官网Doc : Spring Security ](https://docs.spring.io/spring-
security/reference/index.html)


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之前,默认的PasswordEncoderNoOpPasswordEncoder,它需要明文密码。根据Password
History部分,您可能期望默认的PasswordEncoder现在是类似于BCryptPasswordEncoder的东西。然而,这忽略了现实世界中的三个问题:

  1. 许多应用程序使用不容易迁移的旧密码编码。

  2. 密码存储的最佳实践将再次更改。

  3. 作为一个框架,Spring Security不能频繁地进行破坏性更改。

相反,Spring Security引入了DelegatingPasswordEncoder,它通过以下方式解决了所有问题:

  • 确保使用当前密码存储建议对密码进行编码

  • 允许以现代和遗留格式验证密码

  • 允许将来升级编码

你可以使用PasswordEncoderFactories很容易地构造一个DelegatingPasswordEncoder的实例:

PasswordEncoder passwordEncoder =
    PasswordEncoderFactories.createDelegatingPasswordEncoder();

创建自定义DelegatingPasswordEncoder

String idForEncode = "bcrypt";
Map encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
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", new StandardPasswordEncoder());

PasswordEncoder passwordEncoder =
    new DelegatingPasswordEncoder(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 16
    BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
    String result = encoder.encode(“myPassword”);
    assertTrue(encoder.matches(“myPassword”, result));

    // Create an encoder with all the defaults
    Argon2PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
    String result = encoder.encode(“myPassword”);
    assertTrue(encoder.matches(“myPassword”, result));

    // Create an encoder with all the defaults
    Pbkdf2PasswordEncoder encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8();
    String result = encoder.encode(“myPassword”);
    assertTrue(encoder.matches(“myPassword”, result));

    // Create an encoder with all the defaults
    SCryptPasswordEncoder 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,您可以恢复到以前的行为。

@Bean
public static NoOpPasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
}

体系结构 Architecture

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

Servlet 过滤器

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

![FilterChain](https://img-
blog.csdnimg.cn/6865e0706ce445609891a9a280693cd7.png#pic_center)
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。

public void doFilter(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

![在这里插入图片描述](https://img-
blog.csdnimg.cn/bcb64bf9083b45d899a93623c34f4494.png#pic_center)
DelegatingFilterProxyApplicationContext 查找 Bean Filter0,然后调用 Bean Filter0。下面的列表显示了 DelegatingFilterProxy 的伪代码。

public void doFilter(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

  1. 添加到 SecurityFilterChain
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        // ...
        .addFilterBefore(new TenantFilter(), 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
不要向容器注册它:

@Bean
public FilterRegistrationBean<TenantFilter> tenantFilterRegistration(TenantFilter filter) {
    FilterRegistrationBean<TenantFilter> registration = new FilterRegistrationBean<>(filter);
    registration.setEnabled(false);
    return registration;
}
处理 Security 异常

在这里插入图片描述

  1. 首先,ExceptionTranslationFilter 调用 FilterChain.doFilter(request, response) 来调用应用程序的其他部分。

  2. 如果用户没有被认证,或者是一个 AuthenticationException,那么就 开始认证。

* SecurityContextHolder 被清理掉。

* HttpServletRequest 被保存起来,这样一旦认证成功,它就可以用来重放原始请求。

* AuthenticationEntryPoint 用于请求客户的凭证。例如,它可以重定向到一个登录页面或发送一个 WWW-Authenticate 头。
  1. 否则,如果是 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 是否有保存的请求。

@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
	HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
	requestCache.setMatchingRequestParameterName("continue");
	http
		// ...
		.requestCache((cache) -> cache
			.requestCache(requestCache)
		);
	return http.build();
}
防止请求被保存

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

要做到这一点,你可以使用 NullRequestCache 实现.

@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
    RequestCache nullRequestCache = new NullRequestCache();
    http
        // ...
        .requestCache((cache) -> cache
            .requestCache(nullRequestCache)
        );
    return http.build();
}

认证

Servlet 认证架构

[认证官网学习:](https://springdoc.cn/spring-
security/servlet/authentication/architecture.html)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令牌。
![在这里插入图片描述](https://img-
blog.csdnimg.cn/10daa991b3a040cea7a1a1ba784b4635.jpeg)

用 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](https://img-
blog.csdnimg.cn/img_convert/f8383ae544840361c294faa23522d24a.png)
[读取用户名和密码](https://springdoc.cn/spring-
security/servlet/authentication/passwords/index.html#servlet-authentication-
unpwd-input)部分的认证 FilterUsernamePasswordAuthenticationToken 传递给
AuthenticationManager,它由 [ProviderManager](https://springdoc.cn/spring-
security/servlet/authentication/architecture.html#servlet-authentication-
providermanager) 实现。

![number 2](https://img-
blog.csdnimg.cn/img_convert/328a67831897452f4dfd68bf283a9be8.png)
ProviderManager 被配置为使用一个 DaoAuthenticationProvider 类型的
[AuthenticationProvider](https://springdoc.cn/spring-
security/servlet/authentication/architecture.html#servlet-authentication-
authenticationprovider)。

![number 3](https://img-
blog.csdnimg.cn/img_convert/2af9dd13d122cc95b083d13a19b02e52.png)
DaoAuthenticationProviderUserDetailsService 中查找 UserDetails

![number 4](https://img-
blog.csdnimg.cn/img_convert/7ac73d85589ce858c5c8946ce9d265b3.png)
DaoAuthenticationProvider 使用
[PasswordEncoder](https://springdoc.cn/spring-
security/servlet/authentication/passwords/password-encoder.html#servlet-
authentication-password-storage) 来验证上一步返回的 UserDetails 上的密码。

![number 5](https://img-
blog.csdnimg.cn/img_convert/0d3826a012625591a0fa6667ab1c6d20.png) 当认证成功时,返回的
[Authentication](https://springdoc.cn/spring-
security/servlet/authentication/architecture.html#servlet-authentication-
authentication) 是 UsernamePasswordAuthenticationToken
类型,并且有一个委托人(principal)是由配置的 UserDetailsService 返回的 UserDetails。最终,返回的
UsernamePasswordAuthenticationToken 被认证 Filter 设置在
[SecurityContextHolder](https://springdoc.cn/spring-
security/servlet/authentication/architecture.html#servlet-authentication-
securitycontextholder) 上。

UserDetailsService

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

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

@Bean
CustomUserDetailsService customUserDetailsService() {
	return new CustomUserDetailsService();
}
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 = new CookieClearingLogoutHandler("our-custom-cookie");
http
    .logout((logout) -> logout.addLogoutHandler(cookies))

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

HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter());
http
    .logout((logout) -> logout.addLogoutHandler(clearSiteData))

授权

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

默认情况下,AuthorizationFilter 是 Spring Security filter chain 中的最后一个。这意味着
Spring Security 的 authentication filter、漏洞保护 和其他 filter 集成都不需要授权。如果你在
AuthorizationFilter 之前添加自己的 filter,它们也将不需要授权;否则,它们将需要授权。

这一点通常在你添加 Spring MVC 端点时变得很重要。因为它们是由DispatcherServlet执行的,而这是在
AuthorizationFilter 之后,你的端点需要 包含在 authorizeHttpRequests 中才能被允许。

![在这里插入图片描述](https://img-
blog.csdnimg.cn/direct/f0d58c5fbd1d4922aa4c73c59b97d203.png)

  1. 首先,AuthorizationFilter 构造一个 Supplier,从 SecurityContextHolder 中检索一个 Authentication。

  2. 其次,它将 Supplier 和 HttpServletRequest 传递给 AuthorizationManager。AuthorizationManager 将请求与 authorizeHttpRequests 中的模式相匹配,并运行相应的规则。

  3. 如果授权被拒绝,会发布一个 AuthorizationDeniedEvent,并抛出一个 AccessDeniedException。在这种情况下,ExceptionTranslationFilter 会处理 AccessDeniedException。

  4. 如果访问被授权,就会 发布一个 AuthorizationGrantedEvent,AuthorizationFilter 继续进行 FilterChain,允许应用程序正常处理。

授权架构

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

[授权架构](https://springdoc.cn/spring-
security/servlet/authentication/architecture.html)

AuthorizationManager

AuthorizationManager
3.0后, AuthorizationManager 同时取代了 AccessDecisionManager
AccessDecisionVoter

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

AuthorizationDecision check(Supplier<Authentication> authentication, Object secureObject);

default AuthorizationDecision verify(Supplier<Authentication> authentication, Object secureObject)
        throws AccessDeniedException {
    // ...
}

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 说明了相关的类。

![在这里插入图片描述](https://img-
blog.csdnimg.cn/direct/1c5d6083a9f64c5aa7886da7f229375d.png)

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

很明显,你也可以实现一个自定义的 AuthorizationManager,你可以把你想要的任何访问控制逻辑放在里面。

使用路径(Path)参数
http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/resource/{name}").access(new WebExpressionAuthorizationManager("#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 来确定访问权限。

接下来我将给各位同学划分一张学习计划表!

学习计划

那么问题又来了,作为萌新小白,我应该先学什么,再学什么?
既然你都问的这么直白了,我就告诉你,零基础应该从什么开始学起:

阶段一:初级网络安全工程师

接下来我将给大家安排一个为期1个月的网络安全初级计划,当你学完后,你基本可以从事一份网络安全相关的工作,比如渗透测试、Web渗透、安全服务、安全分析等岗位;其中,如果你等保模块学的好,还可以从事等保工程师。

综合薪资区间6k~15k

1、网络安全理论知识(2天)
①了解行业相关背景,前景,确定发展方向。
②学习网络安全相关法律法规。
③网络安全运营的概念。
④等保简介、等保规定、流程和规范。(非常重要)

2、渗透测试基础(1周)
①渗透测试的流程、分类、标准
②信息收集技术:主动/被动信息搜集、Nmap工具、Google Hacking
③漏洞扫描、漏洞利用、原理,利用方法、工具(MSF)、绕过IDS和反病毒侦察
④主机攻防演练:MS17-010、MS08-067、MS10-046、MS12-20等

3、操作系统基础(1周)
①Windows系统常见功能和命令
②Kali Linux系统常见功能和命令
③操作系统安全(系统入侵排查/系统加固基础)

4、计算机网络基础(1周)
①计算机网络基础、协议和架构
②网络通信原理、OSI模型、数据转发流程
③常见协议解析(HTTP、TCP/IP、ARP等)
④网络攻击技术与网络安全防御技术
⑤Web漏洞原理与防御:主动/被动攻击、DDOS攻击、CVE漏洞复现

5、数据库基础操作(2天)
①数据库基础
②SQL语言基础
③数据库安全加固

6、Web渗透(1周)
①HTML、CSS和JavaScript简介
②OWASP Top10
③Web漏洞扫描工具
④Web渗透工具:Nmap、BurpSuite、SQLMap、其他(菜刀、漏扫等)

那么,到此为止,已经耗时1个月左右。你已经成功成为了一名“脚本小子”。那么你还想接着往下探索吗?

阶段二:中级or高级网络安全工程师(看自己能力)

综合薪资区间15k~30k

7、脚本编程学习(4周)
在网络安全领域。是否具备编程能力是“脚本小子”和真正网络安全工程师的本质区别。在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定的编程能力。在分秒必争的CTF竞赛中,想要高效地使用自制的脚本工具来实现各种目的,更是需要拥有编程能力。

零基础入门的同学,我建议选择脚本语言Python/PHP/Go/Java中的一种,对常用库进行编程学习
搭建开发环境和选择IDE,PHP环境推荐Wamp和XAMPP,IDE强烈推荐Sublime;

Python编程学习,学习内容包含:语法、正则、文件、 网络、多线程等常用库,推荐《Python核心编程》,没必要看完

用Python编写漏洞的exp,然后写一个简单的网络爬虫

PHP基本语法学习并书写一个简单的博客系统

熟悉MVC架构,并试着学习一个PHP框架或者Python框架 (可选)

了解Bootstrap的布局或者CSS。

阶段三:顶级网络安全工程师

如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

学习资料分享

当然,只给予计划不给予学习资料的行为无异于耍流氓,这里给大家整理了一份【282G】的网络安全工程师从入门到精通的学习资料包,可点击下方二维码链接领取哦。

  • 17
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot项目中集成Spring Security非常简单,只需要在项目中增加Spring Boot Security的依赖即可。下面是一个基础的Spring Boot Security登录验证的示例: 1.在pom.xml文件中添加Spring Boot Security依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ``` 2.创建一个WebSecurityConfig类,用于配置Spring Security: ```java @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("user").password("password").roles("USER"); } } ``` 3.在上面的代码中,我们配置了一个基本的登录验证,只有经过身份验证的用户才能访问应用程序的其他部分。我们还定义了一个用户“user”,密码为“password”,并将其分配给“USER”角色。 4.在应用程序中,我们还需要一个登录页面。在templates文件夹中创建一个名为“login.html”的文件,添加以下内容: ```html <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8" /> <title>Login</title> </head> <body> <h1>Login</h1> <form th:action="@{/login}" method="post"> <div><label>Username: <input type="text" name="username" /></label></div> <div><label>Password: <input type="password" name="password" /></label></div> <<div><input type="submit" value="Sign In" /></div> </form> </body> </html> ``` 5.最后,在应用程序的控制器中添加一个映射到登录页面的请求处理程序: ```java @Controller public class HomeController { @RequestMapping(value = {"/", "/home"}) public String home() { return "home"; } @RequestMapping(value = "/login") public String login() { return "login"; } } ``` 这样,我们就完成了一个基本的Spring Boot Security登录验证的配置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值