04、Spring Security 实战 - 默认用户是如何生成的?

进入自动配置类 UserDetailsServiceAutoConfiguration :

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(
    value = {
   
      AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class,
             AuthenticationManagerResolver.class },
    type = {
   
      "org.springframework.security.oauth2.jwt.JwtDecoder",
            "org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector",
            "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository" })
public class UserDetailsServiceAutoConfiguration {
   
     

    private static final String NOOP_PASSWORD_PREFIX = "{noop}";

    private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern.compile("^\\{.+}.*$");

    private static final Log logger = LogFactory.getLog(UserDetailsServiceAutoConfiguration.class);

    @Bean
    @Lazy
    public InMemoryUserDetailsManager inMemoryUserDetailsManager(
        SecurityProperties properties,
        ObjectProvider<PasswordEncoder> passwordEncoder) {
   
     
        SecurityProperties.User user = properties.getUser();
        List<String> roles = user.getRoles();
        return new InMemoryUserDetailsManager(
            User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
            .roles(StringUtils.toStringArray(roles)).build());
    }

    private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) {
   
     
        String password = user.getPassword();
        if (user.isPasswordGenerated()) {
   
     
            logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
        }
        if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
   
     
            return password;
        }
        return NOOP_PASSWORD_PREFIX + password;
    }
}

1. 配置类上的注解解析
  • @Conditional:仅当所有指定条件都匹配时,组件才有资格注册:

  • 将 @Conditional 注解加在配置类(@Configuration,@Component)上,来控制是否需要解析和注册这个配置类;

  • 将 @Conditional 注解加在 @Bean注解的方法上,来控制是否需要将该bean到Spring容器中;

  • 如果配置类没有被注册到容器中,那么这个配置类中需要注册的bean都不会注册到容器中;

  • 通常 @Conditional+@Configuration 或者 @Conditional+@Bean 搭配使用;

  • @Configuration(proxyBeanMethods = false):用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被扫描,并用于构建bean定义,初始化Spring容器。

  • @ConditionalOnClass(AuthenticationManager.class):当classpath类路径下存在 AuthenticationManager 类时,条件成立;

  • @ConditionalOnBean(ObjectPostProcessor.class):当Spring容器中存在 ObjectPostProcessor 实例时,条件成立;

  • @ConditionalOnMissingBean(value = { AuthenticationManager.class,AuthenticationProvider.class,UserDetailsService.class,AuthenticationManagerResolver.class },):当Spring容器中不存在AuthenticationManager,AuthenticationProvider,UserDetailsService,AuthenticationManagerResolver 实例时,条件成立;

前两个条件默认是成立的,而代码中并没有定义AuthenticationManager,AuthenticationProvider,UserDetailsService,AuthenticationManagerResolver 实例,因此条件也是满足的,因此Spring容器将进行 UserDetailsServiceAutoConfiguration的解析和注册。

2. 配置类中的@Bean注解方法

通常加了@Configuration注解的配置类中会有一个或者多个加了@Bean注解的方法,这些方法将会被扫描并用于构建bean定义,初始化Spring容器。

在UserDetailsServiceAutoConfiguration配置类中有一个被@Bean注解标注的方法,该方法返回一个InMemoryUserDetailsManager 实例,然后将这个InMemoryUserDetailsManager 对象交给Spring容器管理。

@Bean
@Lazy
public InMemoryUserDetailsManager inMemoryUserDetailsManager(
    SecurityProperties properties,
    ObjectProvider<PasswordEncoder> passwordEncoder) {
   
     
    
    // 从 SecurityProperties类找中获取User对象
    SecurityProperties.User user = properties.getUser();
    
    // 获取User对象的角色
    List<String> roles = user.getRoles();
    
    // 返回一个 InMemoryUserDetailsManager 实例,这个默认用户User对象就存在其中
    return new InMemoryUserDetailsManager(
        User
        .withUsername(user.getName())
        .password(getOrDeducePassword(user,passwordEncoder.getIfAvailable()))
        .roles(StringUtils.toStringArray(roles))
        .build()
    );
}

那么SecurityProperties

3. SecurityProperties 属性配置类
@ConfigurationProperties(prefix = "spring.security")
public class SecurityProperties {
   
     
    // ...
    private final User user = new User();
    
    public User getUser() {
   
     
        return this.user;
    }
    // ...
    public static class User {
   
     
        /**
       * Default user name.
       */
        private String name = "user";
        /**
       * Password for the default user name.
       */
        private String password = UUID.randomUUID().toString();
        /**
       * Granted roles for the default user name.
       */
        private List<String> roles = new ArrayList<>();
        private boolean passwordGenerated = true;
    }
}

@ConfigurationProperties(prefix = “spring.security”) :读取application.properties配置文件中以spring.security开头的属性并封装在属性配置类SecurityProperties中;

这就是默认生成 user 以及 uuid 密码过程! 另外看明白源码之后,就知道只要在配置文件中加入如下配置可以对内存中用户和密码进行覆盖。

spring.security.user.name=root
spring.security.user.password=root
spring.security.user.roles=admin,users