Spring Security 配置
前言
hello world 程序示例中,通过集成了spring security的jar包后,编写了一个hello接口,此时通过测试发现,hello接口已经被保护了,需要进行登录认证才能访问。那么用户名和密码是在哪生成的呢?
一、UserDetailsServiceAutoConfiguration
通过控制台的日志,可以看到输出的随机密码是由UserDetailsServiceAutoConfiguration配置类生成的。
源码如下:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(
value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class },
type = { "org.springframework.security.oauth2.jwt.JwtDecoder",
"org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector" })
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
@ConditionalOnMissingBean(
type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository")
@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;
}
}
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;
public void setPassword(String password) {
if (!StringUtils.hasLength(password)) {
return;
}
this.passwordGenerated = false;
this.password = password;
}
}
通过源码发现,UserDetailsServiceAutoConfiguration配置类添加具有默认用户和生成密码的InMemoryUserDetailsManager 。 这可以通过提供AuthenticationManager 、 AuthenticationProvider或UserDetailsService类型的 bean 来禁用。获取的User类为SecurityProperties的内置类,可通过配置进行赋值。User的默认用户名为 user,默认的密码为UUID.randomUUID().toString() 生成的随机密码。
二、自定义UserDetailsManager
1.InMemoryUserDetailsManager
通过UserDetailsServiceAutoConfiguration配置类,可以看到使用了InMemoryUserDetailsManager。通过查看InMemoryUserDetailsManager源码,可以看到InMemoryUserDetailsManager实现了UserDetailsManager, UserDetailsPasswordService两个接口。
public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {
其中UserDetailsPasswordService定义了一个修改密码的接口:
UserDetails updatePassword(UserDetails user, String newPassword);
再看UserDetailsManager接口,此接口继承了
UserDetailsService,是UserDetailsService的一个拓展接口。而通过UserDetailsServiceAutoConfiguration的@Conditional系列注解,我们可以知道,通过提供UserDetailsService类型的bean,可以禁用自动配置。
2.UserDetailsManager
创建自定义UserDetailsManager
@Bean
public UserDetailsManager userDetailsManager() {
return new UserDetailsManager() {
// 为了测试方便,其他的方法 直接空实现
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User("damon","{noop}1234", Collections.emptyList());
}
};
}
通过loadUserByUsername方法可以看到,传入用户名,返回UserDetails。UserDetails是一个spring security定义的用户抽象接口,包含用户名,密码,权限集合等属性。框架的内部实现类,有一个User类,通过构造函数返回User类,也可以自定义UserDetails的实现类,来扩展手机号,邮箱等字段。在 Spring Security 5.0 之前,默认的 PasswordEncoder 是 NoOpPasswordEncoder,而现在默认是BCryptPasswordEncoder,此时我们设置密码时,需要加上{noop}前缀。来告诉框架此用户使用的是纯文本密码。
3. 再次启动,登录
再次启动程序后,我们会发现控制台已经没有了随机密码的输出日志。证明了我们自定义的UserDetailsManager生效了,切关闭了默认的配置类。
此时访问 hello 接口,弹出认证表单,输入配置的用户名和密码,正常返回 hello world
总结
若想通过用数据库的方式来实现用户的管理,只需要在查询用户的逻辑改成通过调用数据库即可。