覆盖 Spring Security 默认配置

createh51个月前 (03-19)技术教程5

既然您已经知道了第一个项目的默认值,现在就来看看如何替换它们。您需要了解覆盖默认组件的选项,因为这是插入自定义实现和应用安全的方式,因为它适合您的应用程序。而且,正如您将在本篇学到的,开发过程还涉及如何编写配置以保持应用程序的高度可维护性。在我们将要处理的项目中,您通常会发现多种方法来覆盖配置。这种灵活性可能会造成混乱。我经常看到在同一个应用程序中混合配置 Spring Security 的不同部分的不同风格,这是不可取的。因此,这种灵活性需要谨慎。您需要学习如何从这些选项中进行选择,因此这一节也是关于了解您的选项是什么。

在某些情况下,开发人员选择在Spring上下文中使用bean进行配置。在其他情况下,它们为相同的目的覆盖各种方法。Spring生态系统的发展速度可能是产生这些多种方法的主要因素之一。使用混合样式配置项目是不可取的,因为这会使代码难以理解,并影响应用程序的可维护性。了解您的选项以及如何使用它们是一项有价值的技能,它有助于您更好地理解应该如何在项目中配置应用程序级安全性。

在本节中,您将学习如何配置 UserDetailsService 和 PasswordEncoder。这两个组件参与身份验证,大多数应用程序根据它们的需求对它们进行定制。虽然我们将在后面文章讨论定制它们的细节,但了解如何插入定制实现是必要的。我们在本篇中使用的实现都是由 Spring Security 提供的。

覆盖 UserDetailsService 组件

我们在本篇中讨论的第一个组件是 UserDetailsService 。如您所见,应用程序在身份验证过程中使用此组件。在本节中,您将学习如何定义 UserDetailsService 类型的自定义bean。我们这样做是为了覆盖 Spring Security 提供的默认值。您将在后面文章中看到更详细的内容,您可以选择创建自己的实现或使用 Spring Security 提供的预定义实现。在本文中,我们不会详细介绍 Spring Security 提供的实现,也不会创建我们自己的实现。我将使用 Spring Security 提供的一个名为
InMemoryUserDetailsManager 的实现。通过这个示例,您将了解如何将这种对象插入您的架构中。

Java 中的接口定义对象之间的契约。在应用程序的类设计中,我们使用接口来解耦相互使用的对象。在系列文章中讨论这些时,为了加强这种接口特征,我主要将它们称为 contracts。

为了向您展示使用我们所选择的实现覆盖该组件的方法,我们将更改第一个示例中所做的操作。这样做允许我们拥有自己的托管凭证来进行身份验证。对于本例,我们没有实现我们的类,但是我们使用 Spring Security 提供的实现。

在此示例中,我们使用
InMemoryUserDetailsManager 实现。 虽然此实现不仅仅是 UserDetailsService,目前,我们仅从 UserDetailsService 的角度进行引用。 该实现将凭证存储在内存中,然后由 Spring Security 用来验证请求。


InMemoryUserDetailsManager 实现不适合用于生产就绪的应用程序,但它是用于示例或概念证明的出色工具。 在某些情况下,您需要的只是用户。 您无需花费时间来实现此功能的这一部分。 在我们的案例中,我们使用它来了解如何覆盖默认的 UserDetailsService 实现。

我们首先定义一个配置类。 通常,我们在名为config的单独程序包中声明配置类。 清单 2.3显示了配置类的定义。

本 Spring Security 系列的示例是为 Java 11 设计的,Java 11 是最新的长期受支持的Java版本。 因此,我希望生产中会有越来越多的应用程序使用 Java 11。因此在系列的示例中使用此版本非常有意义。

有时您会看到我在代码中使用了 var。 Java 10 引入了保留类型名称var,您只能将其用于本地声明。 在本系列中,我使用它来缩短语法,并隐藏变量类型。 我们将在后面的章节中讨论 var 隐藏的类型,因此您不必担心该类型,直到需要对其进行正确分析为止。

清单 2.3 在配置类中配置 UserDetailsService 的 Bean

//@Configuration 注解将该类标记为配置类。
@Configuration
public class ProjectConfig {
  //@Bean 注解指明 Spring 将返回的值添加为 Spring 上下文中的 Bean。
  @Bean
  public UserDetailsService userDetailsService() {
    //var 词使语法更短,并隐藏了一些细节。
      var userDetailsService = new InMemoryUserDetailsManager();
      return userDetailsService;
  }
}

我们用 @Configuration 注解该类。 @Bean 注解指示 Spring 将方法返回的实例添加到 Spring 上下文中。 如果您完全按照现在的方式执行代码,则在控制台中将不再看到自动生成的密码。 该应用程序现在使用您添加到上下文中的 UserDetailsService 类型的实例,而不是默认的自动配置实例。 但是,同时,由于以下两个原因,您将无法再访问端点:

  • 你没有任何用户。
  • 你没有 PasswordEncoder。

在图 2.2 中,您可以看到身份验证也依赖于 PasswordEncoder。让我们逐步解决这两个问题。我们需要

  1. 创建至少一个拥有一组凭据( username 和 password )的用户
  2. 添加由我们的 UserDetailsService 实现管理的用户
  3. 定义类型为 PasswordEncoder 的 bean,我们的应用程序可以使用该 bean 来验证给定的密码,该密码由 UserDetailsService 存储和管理

首先,我们声明并添加一组凭据,用于向
InMemoryUserDetailsManager 实例进行身份验证。在后面文章中,我们将讨论更多关于用户和如何管理他们的内容。现在,让我们使用一个预定义的构建器来创建一个 UserDetails 类型的对象。

构建实例时,我们必须提供用户名,密码和至少一个授权。 权限是对该用户允许的操作,我们可以为此使用任何字符串。 在清单 2.4 中,我将权限命名为 read,但是由于我们暂时不会使用此权限,因此这个名称并不重要。

清单2.4 使用 User 构建器类为 UserDetailsService 创建用户

@Configuration
public class ProjectConfig {
    @Bean
    public UserDetailsService userDetailsService() {
    		var userDetailsService = new InMemoryUserDetailsManager();
        //使用给定的用户名,密码和权限列表构建用户
        var user = User.withUsername("tom")
        .password("12345")
        .authorities("read")
        .build();
      
        // 添加要由 UserDetailsService 管理的用户
    		userDetailsService.createUser(user);
    		return userDetailsService;
    }
}

您会在
org.springframework.security.core.userdetails 包中找到 User 类。 这是我们用来创建代表用户的对象的构建器实现。 另外,作为本系列文章的一般规则,如果我不介绍如何在代码清单中编写类,则表示 Spring Security 提供了它。

如清单2.4 所示,我们必须为用户名提供一个值,为密码提供一个值,并为权限提供至少一个值。 但这还不足以允许我们调用端点。 我们还需要声明一个 PasswordEncoder。

使用默认的 UserDetailsService 时,也会自动配置 PasswordEncoder。 因为我们覆盖了 UserDetailsService,所以我们还必须声明一个 PasswordEncoder。 现在尝试该示例,在调用端点时会看到一个异常。 尝试进行身份验证时,Spring Security 意识到它不知道如何管理密码,但失败了。 异常看起来像下一个代码段中一样,您应该在应用程序的控制台中看到它。 客户端返回一条 HTTP 401 未经授权的消息和一个空的响应正文:

curl -u tom:12345 http://localhost:8080/hello

调用 APP 的控制台返回:

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for
 the id "null"
at org.springframework.security.crypto.password
 .DelegatingPasswordEncoder$UnmappedIdPasswordEncoder
 .matches(DelegatingPasswordEncoder.java:244)
 ~[spring-security-core-5.1.6.RELEASE.jar:5.1.6.RELEASE]

为了解决这个问题,我们可以在上下文中添加一个 PasswordEncoder bean,就像使用 UserDetailsService 一样。 对于此 bean,我们使用 PasswordEncoder 的现有实现:

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

NoOpPasswordEncoder 实例将密码视为纯文本。它不加密或散列。对于匹配,NoOpPasswordEncoder 只使用String类的底层 equals(Object o) 方法比较字符串。你不应该在产品应用中使用这种类型的 PasswordEncoder。NoOpPasswordEncoder 是一个很好的选择,可以作为你不想关注密码的哈希算法的例子。因此,该类的开发人员将其标记为 @Deprecated,开发环境将显示其名称并删除。

您可以在下面的清单中查看配置类的完整代码。

清单 2.5 配置类的完整定义

@Configuration
public class ProjectConfig {
    @Bean
    public UserDetailsService userDetailsService() {
        var userDetailsService = new InMemoryUserDetailsManager();
        var user = User.withUsername("tom")
        .password("12345")
        .authorities("read")
        .build();
        userDetailsService.createUser(user);
        return userDetailsService;
    }
   
     @Bean
    public PasswordEncoder passwordEncoder() {
   			 return NoOpPasswordEncoder.getInstance();
    }
}

让我们以用户名清单 tom 和密码 12345 的新用户尝试访问端点:

curl -u tom:12345 http://localhost:8080/hello
Hello!

知道单元测试和集成测试的重要性之后,有些人可能已经想知道为什么我们不为示例编写测试。 您实际上会发现与本系列提供的所有示例相关的 Spring Security 集成测试。 但是,为了帮助您专注于每一文章介绍的主题,我将有关测试 Spring Security 集成的讨论分开了,并在后续的一篇中对此进行了详细介绍。

覆盖端点授权配置

如上面所述,对用户进行了新的管理之后,我们现在可以讨论端点的身份验证方法和配置。在后续文章中,您将学习大量关于授权配置的知识。但在深入细节之前,您必须了解总体情况。实现这一点的最好方法是我们的第一个例子。使用缺省配置,所有端点都假定您有一个由应用程序管理的有效用户。此外,默认情况下,应用程序使用 HTTP Basic 身份验证作为授权方法,但您可以轻松覆盖此配置。

将在下一篇学到的,HTTP 基本身份验证并不适合大多数应用程序架构。有时我们想改变它来匹配我们的应用程序。类似地,并不是应用程序的所有端点都需要保护,对于那些需要保护的端点,我们可能需要选择不同的授权规则。要进行这样的更改,我们首先扩展
WebSecurityConfigurerAdapter 类。扩展这个类允许我们覆盖 configure(HttpSecurity http) 方法,如下面的清单所示。

清单 2.6 继承
WebSecurityConfigurerAdapter

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
    // Omitted code
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    				// ...
    }
}

然后,我们可以使用 HttpSecurity 对象的不同方法来更改配置,如下清单所示。

清单 2.7 使用 HttpSecurity 参数更改配置

@Configuration
public class ProjectConfig  extends WebSecurityConfigurerAdapter {
  // Omitted code
  @Override
  protected void configure(HttpSecurity http) throws Exception {
      http.httpBasic();
      // 所有请求都需要认证。
      http.authorizeRequests()
      .anyRequest().authenticated();
  }
}

清单2.7 中的代码使用与默认行为相同的行为配置端点授权。您可以再次调用端点,以查看它的行为是否与前面的测试相同。只需稍加更改,就可以使所有端点都可以访问,而不需要凭据。您将在下面的清单中看到如何做到这一点。

清单 2.8 使用permitAll()更改授权配置

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
  // Omitted code
  @Override
  protected void configure(HttpSecurity http) throws Exception {
      http.httpBasic();
      http.authorizeRequests()
      // 所有请求都不需要经过身份验证。
      .anyRequest().permitAll();
  }
}

现在,我们可以调用 /hello 端点,而不需要凭据。配置中的 permitAll() 调用,连同 anyRequest() 方法,使所有端点都可以访问,而不需要凭据:

curl http://localhost:8080/hello

调用的响应体是

Hello!

本示例的目的是让您了解如何覆盖缺省配置。我们将在后续文章中详细讨论授权。

通过不同的方式进行配置

使用 Spring Security 创建配置的一个令人困惑的方面是使用多种方式配置相同的东西。在本节中,您将学习配置 UserDetailsService 和 PasswordEncoder 的替代方法。了解你所拥有的选项是很重要的,这样你就可以在这系列文章或其他来源如博客和文章中找到这些例子。理解如何以及何时在应用程序中使用它们也很重要。在接下来的文章中,您将看到扩展本节信息的不同示例。

让我们来看第一个项目。在创建默认应用程序之后,我们通过在 Spring 上下文中添加作为 bean 的新实现,成功覆盖了 UserDetailsService 和 PasswordEncoder。让我们找到另一种方法来为 UserDetailsService 和 PasswordEncoder 进行相同的配置。

在配置类中,我们不是将这两个对象定义为 bean,而是通过 configure (
AuthenticationManagerBuilder auth) 方法设置它们。我们从
WebSecurityConfigurerAdapter 类重写这个方法,并使用它的
AuthenticationManagerBuilder 类型参数来设置 UserDetailsService 和 PasswordEncoder,如下面的清单所示。

清单 2.9 在 configure() 中设置 UserDetailsService 和 PasswordEncoder

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
    // Omitted code
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 声明一个UserDetailsSevice将用户存储在内存中
        var userDetailsService =  new InMemoryUserDetailsManager();
      
        // 定义用户及其所有详细信息
        var user = User.withUsername("tom")
        .password("12345")
        .authorities("read")
        .build();
      
        // 添加要由我们的 UserDetailsSevice 管理的用户
        userDetailsService.createUser(user);
      
        // 现在,在 configure() 方法中设置了 UserDetailsService 和 PasswordEncoder。
        auth.userDetailsService(userDetailsService)
        .passwordEncoder(NoOpPasswordEncoder.getInstance());
    }
}

在清单 2.9 中,您可以看到我们声明 UserDetailsService 的方式与清单 2.5 中相同。不同的是,现在这是在第二个被覆盖的方法中本地完成的。我们还调用来自
AuthenticationManagerBuilder 的 userDetailsService() 方法来注册 UserDetailsService 实例。此外,我们调用passwordEncoder()方法来注册 PasswordEncoder。清单2.10 显示了配置类的完整内容。


WebSecurityConfigurerAdapter 类包含三种不同的重载的 configure() 方法。在清单 2.9 中,我们覆盖了与清单 2.8 不同的一个变量。在下一篇中,我们将更详细地讨论这三个方法。

清单 2.10 配置类的完全代码

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            // 创建一个 InMemoryUserDetailsManager() 实例
            var userDetailsService = new InMemoryUserDetailsManager();

      			// 创建一个新用户
            var user = User.withUsername("tom")
                                     .password("12345")
                                     .authorities("read")
                                     .build();
            // 添加要由我们的 UserDetailsService 管理的用户
            userDetailsService.createUser(user);
           
            // 配置 UserDetailsService 和 PasswordEncoder
            auth.userDetailsService(userDetailsService)      
            .passwordEncoder(NoOpPasswordEncoder.getInstance());
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
          
            http.httpBasic();
          
            // 指定所有请求都需要认证
            http.authorizeRequests()
            .anyRequest().authenticated();
    	}
}

这些配置选项都是正确的。在第一个选项中,我们将bean添加到上下文中,允许您将值注入到可能需要它们的另一个类中。但是,如果您不需要这种情况,第二种选择也一样好。但是,我建议您避免混合配置,因为这可能会造成混淆。例如,下面清单中的代码可能会让您想知道 UserDetailsService 和 PasswordEncoder 之间的链接在哪里。

清单 2.11 最小配置样式

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
  
    // 将 PasswordEncoder 设计为 Bean 
  @Bean
  public PasswordEncoder passwordEncoder() {
 		 return NoOpPasswordEncoder.getInstance();
  }
  
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  		var userDetailsService = new InMemoryUserDetailsManager();
  		var user = User.withUsername("tom")
  														.password("12345")
  														.authorities("read")
  														.build();
  
    	userDetailsService.createUser(user);
      // 直接在 configure() 方法中配置 UserDetailsService
  		auth.userDetailsService(userDetailsService);
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
  		http.httpBasic();
  		
    http.authorizeRequests()
  				 .anyRequest().authenticated();
	}
}

从功能上讲,清单2.11 中的代码可以正常工作,但是,我再次建议您避免混合使用两种方法来保持代码的简洁和易于理解。 使用
AuthenticationManagerBuilder,您可以直接配置用户进行身份验证。 在这种情况下,它将为您创建 UserDetailsService。 但是,语法变得更加复杂,可以认为很难阅读。 我已经不止一次地看到了这种选择,即使是在生产就绪型系统中也是如此。 因为我们使用内存中的方法来配置用户,所以该示例可能看起来不错。 但是在生产应用程序中,情况并非如此。 在那里,您可能将用户存储在数据库中或从另一个系统访问它们。 在这种情况下,配置可能会变得很长且丑陋。 清单2.12 显示了为内存用户编写配置的方法。

清单 2.12 配置在内存中管理的用户

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth.inMemoryAuthentication()
				  .withUser("tom")
          .password("12345")
          .authorities("read")
          .and()
           .passwordEncoder(NoOpPasswordEncoder.getInstance());
}

通常,我不推荐这种方法,因为我发现在应用程序中尽可能地分离职责。

覆盖 AuthenticationProvider 默认实现

正如您看到的,Spring Security 组件提供了很大的灵活性,这为我们在将这些组件适应应用程序的架构时提供了很多选择。到目前为止,您已经了解了 Spring Security 架构中 UserDetailsService 和 PasswordEncoder 的用途。您还看到了几种配置它们的方法。现在是时候了解您还可以定制委托给这些组件的 AuthenticationProvider 了。


AuthenticationProvider 实现身份验证逻辑。它接收来自 AuthenticationManager 的请求,并委托 UserDetailsService 查找用户,并将密码验证交给 PasswordEncoder。

图2.3 显示了 AuthenticationProvider ,它实现了身份验证逻辑,并委托给 UserDetailsService 和 PasswordEncoder 进行用户和密码管理。 因此,可以说,在本节中,我们将进一步深入身份验证和授权架构,以学习如何使用 AuthenticationProvider 实现自定义身份验证逻辑。

我建议您尊重 Spring Security 架构中设计的职责。 这种架构松散地结合了细粒度的职责。 这种设计是使 Spring Security 灵活且易于集成到您的应用程序中的事情之一。 但是,取决于您如何利用其灵活性,您也可以更改设计。 您必须谨慎使用这些方法,因为它们会使您的解决方案变得复杂。 例如,您可以选择不再需要 UserDetailsService 或 PasswordEncoder 的方式来覆盖默认的 AuthenticationProvider。 考虑到这一点,以下清单显示了如何创建自定义身份验证提供程序。

清单 2.13 AuthenticationProvider 接口实现

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    			
          // 这里是认证逻辑
    }

    @Override
    public boolean supports(Class authenticationType) {
      
    				// 这里是认证实现的类型
    }
}

authenticate(Authentication authentication) 方法表示身份验证的所有逻辑,因此我们将添加清单2.14 中所示的实现。我将在后面文章详细解释 supports() 方法的用法。目前,我建议您将其实现正常情况,对于当前的例子来说,这不是必须的。

清单 2.14 实现身份验证逻辑

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    
    // getName() 方法由 Authentication 从 Principal 接口继承。
  	String username = authentication.getName();
    String password = String.valueOf(authentication.getCredentials());
    
    // 这种情况通常会调用 UserDetailsService 和 PasswordEncoder 来测试用户名和密码。
  	if ("tom".equals(username) && "12345".equals(password)) {
    		return new UsernamePasswordAuthenticationToken(username, password, Arrays.asList());
    } else {
   		 throw new AuthenticationCredentialsNotFoundException("Error in authentication!");
    }
}

如您所见,此处 if-else 子句的条件是替换 UserDetailsService 和 PasswordEncoder 的职责。 您不需要使用这两个 bean,但是如果您使用用户名和密码进行身份验证,强烈建议您将其管理逻辑分开。 应用它作为 Spring Security 的架构设计的,甚至当你重写认证实现。

您可能会发现通过实现自己的 AuthenticationProvider 来替换身份验证逻辑很有用。 如果默认实现不完全符合您的应用程序要求,则可以决定实现自定义身份验证逻辑。 完整的 AuthenticationProvider 实现看起来像下一个清单中的实现。

清单 2.15 身份验证提供程序的完整实现

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate (Authentication authentication)  throws AuthenticationException {
        
        String username = authentication.getName();
        String password = String.valueOf(authentication.getCredentials());
      
        if ("tom".equals(username) && "12345".equals(password)) {
        				return new UsernamePasswordAuthenticationToken(username, password, Arrays.asList());
        } else {
          				throw new AuthenticationCredentialsNotFoundException("Error!");
        }
    }

    @Override
    public boolean supports(Class authenticationType) {
    				return UsernamePasswordAuthenticationToken.class
    																			.isAssignableFrom(authenticationType);
		}
}

在配置类中,可以在以下清单中所示的 configure(
AuthenticationManagerBuilder auth)方法中注册 AuthenticationProvider。

清单 2.16 注册 AuthenticationProvider 的新实现

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private CustomAuthenticationProvider authenticationProvider;
  
    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
    				auth.authenticationProvider(authenticationProvider);
    }
  
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    				http.httpBasic();
   				  http.authorizeRequests().anyRequest().authenticated();
    }
}

现在,您可以调用端点,该端点可由身份验证逻辑 tom 定义的唯一被识别的用户访问,密码为 12345:

curl -u tom:12345 http://localhost:8080/hello

响应体:

Hello!

在后面文章中,您将了解有关 AuthenticationProvider 以及如何在身份验证过程中覆盖其行为的更多详细信息。 在该章中,我们还将讨论 Authentication 接口及其实现,例如
UserPasswordAuthenticationToken。

在项目中使用多个配置类

在先前实现的示例中,我们仅使用了配置类。 但是,即使对于配置类,也最好将职责分开。 我们需要这种分离,因为配置开始变得更加复杂。 在用于生产环境的应用程序中,与第一个示例相比,您可能有更多的声明。 您还可能会发现拥有多个配置类以使项目更具可读性很有用。

每个责任只有一个类总是一个很好的实践。对于本例,我们可以将用户管理配置与授权配置分开。我们通过定义两个配置类来做到这一点:UserManagementConfig (在清单 2.17 中定义)和WebAuthorizationConfig (在清单 2.18 中定义)。

清单 2.17 定义用户和密码管理的配置类

@Configuration
public class UserManagementConfig {
  
    @Bean
    public UserDetailsService userDetailsService() {
          var userDetailsService = new InMemoryUserDetailsManager();
      
          var user = User.withUsername("tom")
          .password("12345")
          .authorities("read")
          .build();
          
          userDetailsService.createUser(user);
          return userDetailsService;
    }
    
     @Bean
    public PasswordEncoder passwordEncoder() {
    				return NoOpPasswordEncoder.getInstance();
    }
}

在这种情况下,UserManagementConfig 类仅包含两个负责用户管理的 bean:UserDetailsService 和 PasswordEncoder。 我们将这两个对象配置为Bean,因为此类无法扩展
WebSecurityConfigurerAdapter。 下一个清单显示了此定义。

清单 2.18 定义授权管理的配置类

@Configuration
public class WebAuthorizationConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    				http.httpBasic();
    				http.authorizeRequests().anyRequest().authenticated();
    }
}

在这里,WebAuthorizationConfig 类需要扩展
WebSecurityConfigurerAdapter 并重写 configure(HttpSecurity http)方法。

在这种情况下,您不能同时使用两个类来扩展
WebSecurityConfigurerAdapter。 如果这样做,依赖项注入将失败。 您可以通过使用 @Order 注解设置注入优先级来解决依赖项注入。 但是,从功能上讲,这是行不通的,因为配置之间会互相排斥而不是合并。

总结

  • 当您将Spring Security添加到应用程序的依赖项时,Spring Boot提供了一些默认配置。
  • 您实现了用于身份验证和授权的基本组件:UserDetailsService,PasswordEncoder 和 AuthenticationProvider。
  • 您可以使用User类定义用户。 用户至少应具有 username,password 和 authority。 权限是允许用户在应用程序上下文中执行的操作。
  • Spring Security 提供的 UserDetailsService 的简单实现是 InMemoryUserDetailsManager。 您可以将用户添加到 UserDetailsService 的此类实例中,以管理应用程序内存中的用户。
  • NoOpPasswordEncoder 是使用明文密码的 PasswordEncoder 的实现。 此实现对于学习示例和(也许)概念证明很有用,但不适用于可用于生产的应用程序。
  • 您可以使用 AuthenticationProvider 接口规范在应用程序中实现自定义身份验证逻辑。
  • 编写配置的方法有多种,但是在单个应用程序中,您应该选择并坚持一种方法。 这有助于使您的代码更整洁,更易于理解。

相关文章

java 中为什么重写 equals 后需要重写 hashCode

1. equals 和 hashCode 方法之间的关系  这两个方法都是 Object 的方法,意味着 若一个对象在没有重写 这两个方法时,都会默认采用 Object 类中的方法实现,它们的关系为:...

Java—类加载的基本机制和过程

类加载的基本机制和过程运行Java程序,就是执行java这个命令,指定包含main方法的完整类名,以及一个classpath,即类路径。类路径可以有多个,对于直接的class文件,路径是class文件...

SpringBoot自定义自动配置这些知识点你需要了解

前言Spring Boot 是一个快速开发框架,可以简化 Spring 应用程序的开发,其中自定义配置是其中一个非常重要的特性。在 Spring Boot 中,自定义配置允许开发者以自己的方式来配置应...

揭秘JVM双亲委派:Java世界里的“家族传承”如何守护代码安全?

揭秘JVM双亲委派模型:Java世界里的“家族传承”如何守护代码安全?一、什么是双亲委派模型?——Java世界的“家族责任制”在JVM中,双亲委派模型是类加载机制的核心规则。简单来说,它像一个“家族责...

京东大佬问我,java高级技术人员要掌握哪些技术呢?

京东大佬问我,java高级技术人员要掌握哪些技术呢?首先,我得考虑Java高级工程师需要哪些核心技能。基础部分可能包括JVM、多线程、集合框架这些,但高级的话可能需要更深入,比如JVM调优、并发编程的...

java面向对象三大特性:封装、继承、多态——举例说明(转载)

概念封装:封装就是将客观的事物抽象成类,类中存在属于这个类的属性和方法。继承:继承就是把父对象继承过来,这样子类就存在了父类的操作方法,java是单继承,就是只能继承一个父对象。多态:多态就是程序中允...