一、自动配置
在 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})@EnableWebSecuritystatic class WebSecurityEnablerConfiguration {WebSecurityEnablerConfiguration() {}}@Configuration(proxyBeanMethods = false)@ConditionalOnDefaultWebSecuritystatic 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
源码:
@Overrideprotected 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() {}@Beanpublic 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);}));}
}