SpringSecurity 原理初探(一)

1、项目搭建

创建一个SpringBoot项目、引入依赖

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

创建一个controller测试

1
2
3
4
5
6
7
8
9
@RestController
public class HelloController {

@RequestMapping("/hello")
public String hello1(){
return "hello";
}
}

启动项目,并访问/hello

image-20231029173651102

image-20231029173823633

访问hello接口后默认跳转到了登录界面,默认的用户名是 user , 密码在控制台输出

结论:引入spring security依赖后所有的请求都会受到保护

退出:/logout

为什么用户名会是user?,密码会输出在控制台?

因为引入SpringSecurity场景依赖后,SpringSecurity自动配置类就生效了,生效的配置类给容器中放了很多默认组件,组件中有提供了默认用户名和密码

image-20231029174944899

2、UserDetailsServiceAutoConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@Configuration(proxyBeanMethods = false)
//容器中存在这个类时此配置类才会生效
@ConditionalOnClass(AuthenticationManager.class)
//容器中有这个bean时此配置类才会生效
@ConditionalOnBean(ObjectPostProcessor.class)
//容器中没有这些bean时此配置类才会生效
@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" })

//用户信息自动配置类
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
@Lazy
//在内存中放了一个用户信息
public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
ObjectProvider<PasswordEncoder> passwordEncoder) {
//用户信息从properties中读取,SecurityProperties和application全局配置文件绑定
//所以可以在配置文件中自定义用户信息
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;
}
//给密码加上前缀{noop}
return NOOP_PASSWORD_PREFIX + password;
}

}

SecurityProperties配置文件类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@ConfigurationProperties(prefix = "spring.security") //代表这个类和application.yaml文件绑定
public class SecurityProperties {
....

//提供了一个静态内部类
public static class User {

/**
* Default user name.
*/
//可以看到提供了默认的用户名
private String name = "user";

/**
* Password for the default user name.
*/
//密码通过uuid生成
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;
}

}
}

结论:UserDetailsServiceAutoConfiguration配置类在项目启动时往容器中提供了一个默认用户和密码组件

默认的用户名user,密码通过uuid生成并输出在控制台上

通过源码可以知道可以在application配置文件中自定义用户信息并绑定在SecurityProperties类中

1
2
3
4
5
6
spring:
security:
user:
name: aaa
password: bbb
roles: xxxx

3、SecurityAutoConfiguration

SpringSecurity自动配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
//开启配置绑定
@EnableConfigurationProperties(SecurityProperties.class)
//往容器中导入了四个组件
@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
SecurityDataConfiguration.class, ErrorPageSecurityFilterConfiguration.class })
public class SecurityAutoConfiguration {

@Bean
//如果容器中不存在这个bean则生效
@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
//往容器中放了一个身份验证事件发布器
public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}

}

SpringBootWebSecurityConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration(proxyBeanMethods = false)
//条件装配 只有在应用程序没有显式配置自定义的Spring Security配置时,才会执行这个配置类
@ConditionalOnDefaultWebSecurity
@ConditionalOnWebApplication(type = Type.SERVLET)
class SpringBootWebSecurityConfiguration {

@Bean
//最低优先级
@Order(SecurityProperties.BASIC_AUTH_ORDER)
//默认的安全过滤器链
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
//所有请求都需要经过认证,如果没有认证则跳转到登录页,这也是为什么引入SpirngSecurity后所有请求都会被保护
http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
return http.build();
}

}

WebSecurityEnablerConfiguration

如果用户自定义了配置类并添加@EnableWebSecurity注解则此配置不会生效

1
2
3
4
5
6
7
8
9
10
11
@Configuration(proxyBeanMethods = false)
//当名为 BeanIds.SPRING_SECURITY_FILTER_CHAIN(springSecurityFilterChain) 的 Bean 不存在时,才会加载这个配置类
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnClass(EnableWebSecurity.class)
//当应用程序类型是 SERVLET 时,才会加载这个配置类
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
//启用 Spring Security 的 Web 安全性配置
@EnableWebSecurity
class WebSecurityEnablerConfiguration {

}

SecurityDataConfiguration,未生效

ErrorPageSecurityFilterConfiguration 错误页面配置,往容器中放了一个页面拦截器。这个过滤器在处理错误请求时会被触发,用于处理错误页面的安全性。这些配置允许你自定义如何保护错误页面的访问权限。

4、SecurityFilterAutoConfiguration

让用户可以自定义自己的过滤器-可以在application.yaml中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//在 SecurityAutoConfiguration 自动配置类之后进行配置。
@AutoConfigureAfter(SecurityAutoConfiguration.class)
public class SecurityFilterAutoConfiguration {

@Bean
//当名为 springSecurityFilterChain 的 Bean 存在时生效
@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
SecurityProperties securityProperties) {
DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
DEFAULT_FILTER_NAME);
registration.setOrder(securityProperties.getFilter().getOrder());
registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
return registration;
}

}