依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
添加一个首页
index.html
写入
<h1>hello</h1>
启动应用
得到一个随机密码
Using generated security password: 76f93495-f352-4c5e-b1df-b5348d6cdc2b
访问
http://localhost:8080/
会被重定向到
http://localhost:8080/login
输入用户名
user
密码为刚才的随机密码
76f93495-f352-4c5e-b1df-b5348d6cdc2b
登录成功
分析1-打印的日志来自哪?
Using generated security password
我们搜索一下jar包里面内容,idea如何操作,请参考:
我们通过搜索可以找到代码来自于两个地方,我们通过各自打断点可以证实这个输出来自于:UserDetailsServiceAutoConfiguration的86行(根据你的版本不同,可能有差异)。
通过这里可以看到 生成的密码,以及有一个encoder为null。
user是什么呢?
我们查看一下
从这里看出来,它写死的用户名就是user,roles的size为0,没有角色。
密码的实现
很显然这是通过UUID来实现的,并且这里使用的User就是一个静态内部类,处于SecurityProperties这个类当中。
其源码如下:
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 String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
if (!StringUtils.hasLength(password)) {
return;
}
this.passwordGenerated = false;
this.password = password;
}
public List<String> getRoles() {
return this.roles;
}
public void setRoles(List<String> roles) {
this.roles = new ArrayList<>(roles);
}
public boolean isPasswordGenerated() {
return this.passwordGenerated;
}
}
密码生成的过程
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;
}
- 密码默认是UUID
- 并且isPasswordGenerated判断必然为true,然后打印日志
- 如果encoder不等于null的情况下或者密码满足后面的一个匹配算法则返回密码
- 返回一个 {noop} + 密码
生成密码的方法用于什么?
在inMemoryUserDetailsManager方法中被调用
@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());
}
整体生成了一个InMemoryUserDetailsManager ,叫做内存型的用户细节管理器,见名知意了,比如有哪些用户,用户名密码都是什么之类的细节。
看看这个类的构造方法
public InMemoryUserDetailsManager(UserDetails... users) {
for (UserDetails user : users) {
createUser(user);
}
}
接受不定长参数,可以放入任意的UserDetails。
所以
User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
.roles(StringUtils.toStringArray(roles)).build()
这是在生成一个UserDetails,意味着未来我们有自定义的用户的时候也需要和UserDetails打交道。
这个构造模式做了几件事情
- 指定用户名
- 指定密码
- 指定角色们
这个密码入参就是调用了上面的getOrDeducePassword
方法,也就是日志打印的地方。
总结
出现了系列的重要类级别文件,肯定需要我们进一步研究
- PasswordEncoder
- User (spring中定义的)
- UserDetails
- UserDetailsManager
我们通过命名法可以绘制一个简单关系图
后续我们就要挨个分析他们。
源码
https://github.com/qiudaozhang/spring-security-learn001