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 默认生效的条件有三种情况:
- 在 classpath 下存在 AuthenticationManager 类
- 当前应用中,存在 ObjectPostProcessor 类的实例时
- 当前应用中,不存在 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