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

访问hello接口后默认跳转到了登录界面,默认的用户名是 user , 密码在控制台输出
结论:引入spring security依赖后所有的请求都会受到保护
退出:/logout
为什么用户名会是user?,密码会输出在控制台?
因为引入SpringSecurity场景依赖后,SpringSecurity自动配置类就生效了,生效的配置类给容器中放了很多默认组件,组件中有提供了默认用户名和密码
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)
@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" })
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) { 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; }
}
|
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") public class SecurityProperties { .... public static class User {
private String name = "user";
private String password = UUID.randomUUID().toString();
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 @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)
@ConditionalOnDefaultWebSecurity @ConditionalOnWebApplication(type = Type.SERVLET) class SpringBootWebSecurityConfiguration {
@Bean @Order(SecurityProperties.BASIC_AUTH_ORDER) SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { 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)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN) @ConditionalOnClass(EnableWebSecurity.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@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
| @AutoConfigureAfter(SecurityAutoConfiguration.class) public class SecurityFilterAutoConfiguration {
@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; }
}
|