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

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

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

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

UserDetailServiceAutoConfigutation 类

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

java 复制代码
@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()构造方法

java 复制代码
	public InMemoryUserDetailsManager(UserDetails... users) {
		for (UserDetails user : users) {
			createUser(user);
		}
	}

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

java 复制代码
	@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 获取用户认证信息

java 复制代码
	@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 实例即可

java 复制代码
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 的配置文件中对内存用户和密码进行设置:

xml 复制代码
spring.security.user.name=admin
spring.security.user.password=123

相关推荐
间彧2 分钟前
Spring Boot集成WebSocket项目实战详解
后端
xyy202530 分钟前
Spring事务的传播方式
java·数据库·spring
该用户已不存在1 小时前
工具用得好,Python写得妙,9个效率工具你值得拥有
后端·python·编程语言
深色風信子2 小时前
SpringBoot 集成 LangChain4j RAG Redis 搜索
spring boot·langchain4j rag·rag redis 搜索·rag redis·springboot rag·rag 搜索
im_AMBER2 小时前
Web 开发 30
前端·笔记·后端·学习·web
码事漫谈2 小时前
LLVM IR深度技术解析:架构、优化与应用
后端
码事漫谈3 小时前
C++ 中的类型转换:深入理解 static_cast 与 C风格转换的本质区别
后端
小蒜学长3 小时前
springboot餐厅信息管理系统设计(代码+数据库+LW)
java·数据库·spring boot·后端
Chh432243 小时前
React 新版
后端
Jabes.yang3 小时前
Java大厂面试实录:从Spring Boot到微服务的技术探讨
java·spring boot·spring cloud·微服务·技术面试