一、自动配置
在 Spring Security 6.1.x 系列(1)—— 初识Spring Security 中我们只引入spring-boot-starter-security
依赖,就可以实现登录认证,这些都得益于Spring Boot
的自动配置。
在spring-boot-autoconfigure
模块中集成了对Spring Security
的自动配置:
默认的配置是由 SecurityAutoConfiguration
、 UserDetailsServiceAutoConfiguration
、SecurityFilterAutoConfiguration
这三个自动配置类实现的。
二、SecurityAutoConfiguration
SecurityAutoConfiguration
主要是导入SpringBootWebSecurityConfiguration
配置:
@AutoConfiguration(
before = {UserDetailsServiceAutoConfiguration.class}
)
@ConditionalOnClass({DefaultAuthenticationEventPublisher.class})
@EnableConfigurationProperties({SecurityProperties.class})
@Import({SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class})
public class SecurityAutoConfiguration {
public SecurityAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean({AuthenticationEventPublisher.class})
public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}
}
2.1 SpringBootWebSecurityConfiguration
在SpringBootWebSecurityConfiguration
配置类中,默认添加了@EnableWebSecurity
注解,启用了Spring Security
应用安全配置,并完成defaultSecurityFilterChain
构建:
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnWebApplication(
type = Type.SERVLET
)
class SpringBootWebSecurityConfiguration {
SpringBootWebSecurityConfiguration() {
}
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnMissingBean(
name = {"springSecurityFilterChain"}
)
@ConditionalOnClass({EnableWebSecurity.class})
@EnableWebSecurity
static class WebSecurityEnablerConfiguration {
WebSecurityEnablerConfiguration() {
}
}
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnDefaultWebSecurity
static class SecurityFilterChainConfiguration {
SecurityFilterChainConfiguration() {
}
@Bean
@Order(2147483642)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> {
// 配置所有的Http请求必须认证
((AuthorizeHttpRequestsConfigurer.AuthorizedUrl)requests.anyRequest()).authenticated();
});
// 开启表单登录认证
http.formLogin(Customizer.withDefaults());
// 开启basic认证
http.httpBasic(Customizer.withDefaults());
return (SecurityFilterChain)http.build();
}
}
}
2.1.1 DefaultSecurityFilterChain构建
Spring Security
提供了默认实现类DefaultSecurityFilterChain
,上文源码中defaultSecurityFilterChain
通过HttpSecurity#build
方法构建,跟踪进入HttpSecurity#performBuild
源码:
从上面看得出HttpSecurity
就是一个构建类,它的使命就是构建出一个SecurityFilterChain
,查看DefaultSecurityFilterChain
默认匹配所有请求,并默认存在15存在过滤器:
2.2 @EnableWebSecurity
@EnableWebSecurity
中会导入多个配置类:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class, HttpSecurityConfiguration.class})
@EnableGlobalAuthentication
public @interface EnableWebSecurity {
boolean debug() default false;
}
下文重点介绍下WebSecurityConfiguration
配置类。
2.2.1 springSecurityFilterChain构建
springSecurityFilterChain
在WebSecurityConfiguration
中通过WebSecurity#build
方法构建:
跟踪进入WebSecurity#performBuild
源码:
@Override
protected Filter performBuild() throws Exception {
Assert.state(!this.securityFilterChainBuilders.isEmpty(),
() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
+ "Typically this is done by exposing a SecurityFilterChain bean. "
+ "More advanced users can invoke " + WebSecurity.class.getSimpleName()
+ ".addSecurityFilterChainBuilder directly");
int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
List<RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>>> requestMatcherPrivilegeEvaluatorsEntries = new ArrayList<>();
for (RequestMatcher ignoredRequest : this.ignoredRequests) {
WebSecurity.this.logger.warn("You are asking Spring Security to ignore " + ignoredRequest
+ ". This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.");
SecurityFilterChain securityFilterChain = new DefaultSecurityFilterChain(ignoredRequest);
securityFilterChains.add(securityFilterChain);
requestMatcherPrivilegeEvaluatorsEntries
.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
}
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
SecurityFilterChain securityFilterChain = securityFilterChainBuilder.build();
securityFilterChains.add(securityFilterChain);
requestMatcherPrivilegeEvaluatorsEntries
.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
}
if (this.privilegeEvaluator == null) {
this.privilegeEvaluator = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
requestMatcherPrivilegeEvaluatorsEntries);
}
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if (this.httpFirewall != null) {
filterChainProxy.setFirewall(this.httpFirewall);
}
if (this.requestRejectedHandler != null) {
filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler);
}
else if (!this.observationRegistry.isNoop()) {
CompositeRequestRejectedHandler requestRejectedHandler = new CompositeRequestRejectedHandler(
new ObservationMarkingRequestRejectedHandler(this.observationRegistry),
new HttpStatusRequestRejectedHandler());
filterChainProxy.setRequestRejectedHandler(requestRejectedHandler);
}
filterChainProxy.setFilterChainDecorator(getFilterChainDecorator());
filterChainProxy.afterPropertiesSet();
Filter result = filterChainProxy;
if (this.debugEnabled) {
this.logger.warn("\n\n" + "********************************************************************\n"
+ "********** Security debugging is enabled. *************\n"
+ "********** This may include sensitive information. *************\n"
+ "********** Do not use in a production system! *************\n"
+ "********************************************************************\n\n");
result = new DebugFilter(filterChainProxy);
}
this.postBuildAction.run();
return result;
}
springSecurityFilterChain
会被FilterChainProxy
代理,注册为Bean
,并存放了所有的SecurityFilterChain
:
FilterChainProxy
是一个GenericFilterBean
(即是Servlet Filter
又是Spring Bean
),它管理了所有注入Spring IoC
容器的SecurityFilterChain
。
三、UserDetailsServiceAutoConfiguration
UserDetailsServiceAutoConfiguration
只是通过加载Yml
配置文件生成一个默认用户,以便于开发测试:
@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) {
SecurityProperties.User user = properties.getUser();
List<String> roles = user.getRoles();
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;
}
}
看到这里我们就能明白解 Spring Security 6.1.x 系列(1)—— 初识Spring Security 中通过在配置文件中定义用户名和密码能生效的原因。
四、SecurityFilterAutoConfiguration
在SecurityFilterAutoConfiguration
自动配置类中,springSecurityFilterChain
之前被声明构建过,为FilterChainProxy
类型,代理了Spring Security
中所有的SecurityFilterChain
。
因为Servlet
容器和Spring IoC
容器之间的Filter
生命周期并不匹配。
为了让Spring IoC
容器管理Filter
的生命周期,FilterChainProxy
(也就是springSecurityFilterChain
)便交由Spring Web
下的DelegatingFilterProxy
来代理。
而且FilterChainProxy
不会在任何过滤器Bean
上调用标准Servlet
过滤器生命周期方法,FilterChainProxy
的生命周期方法会委托给DelegatingFilterProxy
来执行。
而DelegatingFilterProxy
作为Servlet
容器和Spring IoC
容器的生命周期之间桥接的存在。
@AutoConfiguration(
after = {SecurityAutoConfiguration.class}
)
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@EnableConfigurationProperties({SecurityProperties.class})
@ConditionalOnClass({AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class})
public class SecurityFilterAutoConfiguration {
private static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";
public SecurityFilterAutoConfiguration() {
}
@Bean
@ConditionalOnBean(
name = {"springSecurityFilterChain"}
)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(SecurityProperties securityProperties) {
DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean("springSecurityFilterChain", new ServletRegistrationBean[0]);
registration.setOrder(securityProperties.getFilter().getOrder());
registration.setDispatcherTypes(this.getDispatcherTypes(securityProperties));
return registration;
}
private EnumSet<DispatcherType> getDispatcherTypes(SecurityProperties securityProperties) {
return securityProperties.getFilter().getDispatcherTypes() == null ? null : (EnumSet)securityProperties.getFilter().getDispatcherTypes().stream().map((type) -> {
return DispatcherType.valueOf(type.name());
}).collect(Collectors.toCollection(() -> {
return EnumSet.noneOf(DispatcherType.class);
}));
}
}