SpringSecurity6从入门到实战之初始用户如何存储到内存

本文涉及的产品
可观测监控 Prometheus 版,每月50GB免费额度
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
注册配置 MSE Nacos/ZooKeeper,182元/月
简介: Spring Security 在 SpringBoot 应用中默认使用 `UserDetailsServiceAutoConfiguration` 类将用户信息存储到内存中。当classpath有`AuthenticationManager`、存在`ObjectPostProcessor`实例且无特定安全bean时,此配置生效。`inMemoryUserDetailsManager()`方法创建内存用户,通过`UserDetails`对象填充`InMemoryUserDetailsManager`的内部map。若要持久化到数据库,需自定义`UserDetailsService`接口实

SpringSecurity6从入门到实战之初始用户如何存储到内存

文接上回,根据登录表单的提交最终得知用户相关信息存储在内存中.那么SpringSecurity是如何在项目启动时将用户信息存储到内存中的呢?

这里我们还是先回到SpringBoot加载配置的地方

UserDetailServiceAutoConfigutation 类

在 SpringBoot 的自动装配中,默认会启动配置类 UserDetailServiceAutoConfigutation ,我们接下来进入UserDetailServiceAutoConfigutation 的源码中看看

@AutoConfiguration
@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", "org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository"}
)
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);
    public UserDetailsServiceAutoConfiguration() {
    }
    @Bean
    public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties, ObjectProvider<PasswordEncoder> passwordEncoder) {
        //这里可以看到获取bean对象的user属性,配置文件中有则获取配置文件内容没有则使用默认值
        SecurityProperties.User user = properties.getUser();
        List<String> roles = user.getRoles();
        //最终返回带user属性的InMemoryUserDetailsManager对象
        return new InMemoryUserDetailsManager(new UserDetails[]{User.withUsername(user.getName()).password(this.getOrDeducePassword(user, (PasswordEncoder)passwordEncoder.getIfAvailable())).roles(StringUtils.toStringArray(roles)).build()});
    }
    private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) {
        String password = user.getPassword();
        if (user.isPasswordGenerated()) {
            logger.warn(String.format("%n%nUsing generated security password: %s%n%nThis generated password is for development use only. Your security configuration must be updated before running your application in production.%n", user.getPassword()));
        }
        return encoder == null && !PASSWORD_ALGORITHM_PATTERN.matcher(password).matches() ? "{noop}" + password : password;
    }
}

配置类 UserDetailServiceAutoConfigutation 默认生效的条件有三种情况:

  1. 在 classpath 下存在 AuthenticationManager 类
  2. 当前应用中,存在 ObjectPostProcessor 类的实例时
  3. 当前应用中,不存在 AuthenticationManager.class、 AuthenticationProvider.class、UserDetailsService.class、     AuthenticationManagerResolver.class 的实例时

这里看到了inMemoryUserDetailsManager()将user属性封装后传到了new InMemoryUserDetailsManager()中作为参数,那么我们继续看向new InMemoryUserDetailsManager()构造方法

public InMemoryUserDetailsManager(UserDetails... users) {
    for (UserDetails user : users) {
      createUser(user);
    }
  }

可以看到将传入的user对象进行了创建操作,那么继续看到createUser()方法

@Override
  public void createUser(UserDetails user) {
    Assert.isTrue(!userExists(user.getUsername()), "user should not exist");
    this.users.put(user.getUsername().toLowerCase(), new MutableUser(user));
  }

这里可以看到this.users就是直接说在内存中存放user信息的map集合,将user信息一个个存入map中.在 InMemoryUserDetailsManager 类中的 loadUserByUsername() 方法中,在 map 集合 users 中根据 username 获取用户认证信息

@Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //在这里进行获取用户认证信息
    UserDetails user = this.users.get(username.toLowerCase());
    if (user == null) {
      throw new UsernameNotFoundException(username);
    }
    return new User(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(),
        user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
  }

这里已经知道了SpringSecurity为我们提供的初始用户时如何存储在内存中了,那么在日常开发中肯定不会将用户认证信息存储在内存中.一定是持久化到数据库里,那么我们应该如何进行操作?

UserDetailService 接口

在 UserDetailService 接口中,loadUserByUserName() 方法用于根据用户名进行认证,默认基于内存实现,不需要有后端数据库的支持。如果想修改成数据库实现,我们只需要自定义 UserDetailService 接口的实现类,并返回 UserDetails 实例即可

package org.springframework.security.core.userdetails;
/**
 * Core interface which loads user-specific data.
 * <p>
 * It is used throughout the framework as a user DAO and is the strategy used by the
 * {@link org.springframework.security.authentication.dao.DaoAuthenticationProvider
 * DaoAuthenticationProvider}.
 *
 * <p>
 * The interface requires only one read-only method, which simplifies support for new
 * data-access strategies.
 *
 * @author Ben Alex
 * @see org.springframework.security.authentication.dao.DaoAuthenticationProvider
 * @see UserDetails
 */
public interface UserDetailsService {
  /**
   * Locates the user based on the username. In the actual implementation, the search
   * may possibly be case sensitive, or case insensitive depending on how the
   * implementation instance is configured. In this case, the <code>UserDetails</code>
   * object that comes back may have a username that is of a different case than what
   * was actually requested..
   * @param username the username identifying the user whose data is required.
   * @return a fully populated user record (never <code>null</code>)
   * @throws UsernameNotFoundException if the user could not be found or the user has no
   * GrantedAuthority
   */
  UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

实现这个接口即可

修改默认用户

在 SecurityProperties 配置类中,定义了 SpringBoot 配置文件中的内容可以自动绑定到 Bean 的属性上:

于是,我们可以在 SpringBoot 的配置文件中对内存用户和密码进行设置:

spring.security.user.name=admin
spring.security.user.password=123

相关文章
|
2月前
|
存储
阿里云轻量应用服务器收费标准价格表:200Mbps带宽、CPU内存及存储配置详解
阿里云香港轻量应用服务器,200Mbps带宽,免备案,支持多IP及国际线路,月租25元起,年付享8.5折优惠,适用于网站、应用等多种场景。
479 0
|
2月前
|
存储 缓存 NoSQL
内存管理基础:数据结构的存储方式
数据结构在内存中的存储方式主要包括连续存储、链式存储、索引存储和散列存储。连续存储如数组,数据元素按顺序连续存放,访问速度快但扩展性差;链式存储如链表,通过指针连接分散的节点,便于插入删除但访问效率低;索引存储通过索引表提高查找效率,常用于数据库系统;散列存储如哈希表,通过哈希函数实现快速存取,但需处理冲突。不同场景下应根据访问模式、数据规模和操作频率选择合适的存储结构,甚至结合多种方式以达到最优性能。掌握这些存储机制是构建高效程序和理解高级数据结构的基础。
165 0
|
2月前
|
存储 弹性计算 固态存储
阿里云服务器配置费用整理,支持一万人CPU内存、公网带宽和存储IO性能全解析
要支撑1万人在线流量,需选择阿里云企业级ECS服务器,如通用型g系列、高主频型hf系列或通用算力型u1实例,配置如16核64G及以上,搭配高带宽与SSD/ESSD云盘,费用约数千元每月。
151 0
|
10月前
|
Java 数据库连接 测试技术
SpringBoot入门 - 添加内存数据库H2
SpringBoot入门 - 添加内存数据库H2
568 3
SpringBoot入门 - 添加内存数据库H2
|
10月前
|
Java 数据库连接 测试技术
SpringBoot入门(4) - 添加内存数据库H2
SpringBoot入门(4) - 添加内存数据库H2
170 4
SpringBoot入门(4) - 添加内存数据库H2
|
9月前
|
存储 缓存 监控
Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
本文介绍了Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
842 7
|
10月前
|
Java 数据库连接 测试技术
SpringBoot入门(4) - 添加内存数据库H2
SpringBoot入门(4) - 添加内存数据库H2
132 13
|
10月前
|
Java 数据库连接 测试技术
SpringBoot入门(4) - 添加内存数据库H2
SpringBoot入门(4) - 添加内存数据库H2
166 4
|
11月前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
708 1