目錄
1 概述
默認Springboot Security安全配置自動加載在SecurityAutoConfiguration和UserDetailsServiceAutoConfiguration。
具體請看spring-boot-autoconfigure-2.0.7.RELEASE.jar下的/META-INF/spring.factories文件
SecurityAutoConfiguration類:導入SpringBootWebSecurityConfiguration攔截響應的請求;UserDetailsServiceAutoConfiguration類:配置身份驗證。
@Configuration
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {
@Bean
@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
public DefaultAuthenticationEventPublisher authenticationEventPublisher(
ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}
}
@Configuration
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnBean(ObjectPostProcessor.class)
// 沒有AuthenticationManager、AuthenticationProvider和UserDetailsService,加載該bean配置系想你
@ConditionalOnMissingBean({ AuthenticationManager.class, AuthenticationProvider.class,
UserDetailsService.class })
public class UserDetailsServiceAutoConfiguration {
....省略
}
1.1 關閉默認Web應用程序安全配置
如果要完全關閉默認Web應用程序安全配置,您可以添加bean繼承 WebSecurityConfigurerAdapter,這樣Springboot將不會初始化默認的WebSecurityConfigurerAdapter (這樣做不會禁用UserDetailsService配置或Actuator的安全性)。
SpringBootWebSecurityConfiguration 的代碼如下:
@Configuration
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
// 沒有WebSecurityConfigurerAdapter類或子類加載默認配置
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
public class SpringBootWebSecurityConfiguration {
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER)
static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {
}
}
關閉UserDetailsService的配置,您可以添加類繼承 UserDetailsService,AuthenticationProvider或AuthenticationManager,來覆蓋Springboot默認實現。
UserDetailsServiceAutoConfiguration 代碼如下:
@Configuration
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnBean(ObjectPostProcessor.class)
// 沒有AuthenticationManager、AuthenticationProvider和UserDetailsService,加載該bean配置系想你
@ConditionalOnMissingBean({ AuthenticationManager.class, AuthenticationProvider.class,
UserDetailsService.class })
public class UserDetailsServiceAutoConfiguration {
@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());
}
}
2 流程分析
經過上面分析我們知道怎麼關閉Springboot默認實現,接着我們看SecurityAutoConfiguration 類中導入的WebSecurityEnablerConfiguration配置信息
@Configuration
@ConditionalOnBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableWebSecurity
public class WebSecurityEnablerConfiguration {
}
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
/**
* Controls debugging support for Spring Security. Default is false.
* @return if true, enables debug support with Spring Security
*/
boolean debug() default false;
}
查看源碼我們發現WebSecurityConfiguration配置了Security關鍵信息
@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
private WebSecurity webSecurity;
private Boolean debugEnabled;
private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;
private ClassLoader beanClassLoader;
@Autowired(required = false)
private ObjectPostProcessor<Object> objectObjectPostProcessor;
......省略
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = webSecurityConfigurers != null
&& !webSecurityConfigurers.isEmpty();
if (!hasConfigurers) {
WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
.postProcess(new WebSecurityConfigurerAdapter() {
});
webSecurity.apply(adapter);
}
return webSecurity.build();
}
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor<Object> objectPostProcessor,
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception {
webSecurity = objectPostProcessor
.postProcess(new WebSecurity(objectPostProcessor));
if (debugEnabled != null) {
webSecurity.debug(debugEnabled);
}
Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
Object previousConfig = null;
for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
if (previousOrder != null && previousOrder.equals(order)) {
throw new IllegalStateException(
"@Order on WebSecurityConfigurers must be unique. Order of "
+ order + " was already used on " + previousConfig + ", so it cannot be used on "
+ config + " too.");
}
previousOrder = order;
previousConfig = config;
}
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
......省略
}
2.1 setFilterChainProxySecurityConfigurer()方法
開始時會將所有實現SecurityConfigurer接口的類注入到webSecurityConfigurers參數中
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor<Object> objectPostProcessor,
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception {
// 創建webSecurity對象 ,應爲是new來的對象,沒有走Spring的生命週期,可以利用objectPostProcessor讓new來的對象走Spring的生命週期
webSecurity = objectPostProcessor
.postProcess(new WebSecurity(objectPostProcessor));
if (debugEnabled != null) {
webSecurity.debug(debugEnabled);
}
// 主要檢驗了配置實例的order順序
Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
Object previousConfig = null;
for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
if (previousOrder != null && previousOrder.equals(order)) {
throw new IllegalStateException(
"@Order on WebSecurityConfigurers must be unique. Order of "
+ order + " was already used on " + previousConfig + ", so it cannot be used on "
+ config + " too.");
}
previousOrder = order;
previousConfig = config;
}
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
// 將所有的配置實例存放進入到webSecurity對象中,其中配置實例中含有我們自定義業務的權限控制配置信息
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
該方法中 主要執行如下
1、創建webSecurity對象
2、主要檢驗了配置實例的order順序(order唯一 否則會報錯)
3、將所有的配置實例存放進入到webSecurity對象中,其中配置實例中含有我們自定義業務的權限控制配置信息
2.2 springSecurityFilterChain()方法
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = webSecurityConfigurers != null
&& !webSecurityConfigurers.isEmpty();
// 這個方法會判斷我們上一個方法中有沒有獲取到webSecurityConfigurers,沒有的話這邊會創建一個WebSecurityConfigurerAdapter實例,並追加到websecurity中
if (!hasConfigurers) {
WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
.postProcess(new WebSecurityConfigurerAdapter() {
});
webSecurity.apply(adapter);
}
// 調用websecurity的build方法
return webSecurity.build();
}
該方法中 主要執行如下
1、這個方法會判斷我們上一個方法中有沒有獲取到webSecurityConfigurers,沒有的話這邊會創建一個WebSecurityConfigurerAdapter實例,並追加到websecurity中
2、調用websecurity的build方法
2.2.1 websecurity的build()方法
SecurityBuilder繼承關係:
AbstractSecurityBuilder類build()方法
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
private AtomicBoolean building = new AtomicBoolean();
private O object;
public final O build() throws Exception {
if (this.building.compareAndSet(false, true)) {
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
}
......省略
}
調用子類AbstractConfiguredSecurityBuilder的doBuild()方法
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
extends AbstractSecurityBuilder<O> {
@Override
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING;
beforeInit();
init();
buildState = BuildState.CONFIGURING;
beforeConfigure();
configure();
buildState = BuildState.BUILDING;
O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}
protected void beforeInit() throws Exception {
}
@SuppressWarnings("unchecked")
private void init() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.init((B) this);
}
for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
configurer.init((B) this);
}
}
protected void beforeConfigure() throws Exception {
}
@SuppressWarnings("unchecked")
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);
}
}
......省略
}
build過程主要分三步,init->configure->peformBuild ,具體流程圖
2.2.1.1 init方法
1、調用getHttp()方法獲取一個http實例,getHttp()方法
Ⅰ、添加認證的事件的發佈者
Ⅱ、配置認證管理器,會調用我們的繼承的WebSecurityConfigurerAdapter中重寫的
configure(AuthenticationManagerBuilder auth)方法,配置認證相關信息
Ⅲ、追加各種SecurityConfigurer的具體實現類到httpSecurity中,如exceptionHandling()方法會追加一個
ExceptionHandlingConfigurer,sessionManagement()方法會追加一個SessionManagementConfigurer,
securityContext()方法會追加一個SecurityContextConfigurer對象,
這些SecurityConfigurer的具體實現類最終會爲我們配置各種具體的filter
Ⅳ、最終調用我們的繼承的WebSecurityConfigurerAdapter中重寫的configure(HttpSecurity http),
將我們業務相關的權限配置規則信息進行初始化操作。默認的configure(HttpSecurity http)方法繼續向httpSecurity類中追加
SecurityConfigurer的具體實現類,如authorizeRequests()方法追加一個ExpressionUrlAuthorizationConfigurer,
formLogin()方法追加一個FormLoginConfigurer。 其中ExpressionUrlAuthorizationConfigurer這個實現類比較重要,
因爲他會給我們創建一個非常重要的對象FilterSecurityInterceptor對象,FormLoginConfigurer對象比較簡單,
但是也會爲我們提供一個在安全認證過程中經常用到會用的一個Filter:UsernamePasswordAuthenticationFilter。
2、通過web.addSecurityFilterChainBuilder方法把獲取到的HttpSecurity實例賦值給WebSecurity的securityFilterChainBuilders屬性
從init可以看出一個WebSecurity可以生成多個HttpSecurity,而在HttpSecurity又會創建多個攔截器,最終封裝到FilterChainProxy對象中
以上三個方法就是WebSecurityConfigurerAdapter類中init方法的主要邏輯,
@Order(100)
public abstract class WebSecurityConfigurerAdapter implements
WebSecurityConfigurer<WebSecurity> {
public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
public void run() {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
}
});
}
@SuppressWarnings({ "rawtypes", "unchecked" })
protected final HttpSecurity getHttp() throws Exception {
if (http != null) {
return http;
}
//添加認證的事件的發佈者
DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
.postProcess(new DefaultAuthenticationEventPublisher());
localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
// 配置認證管理器
AuthenticationManager authenticationManager = authenticationManager();
authenticationBuilder.parentAuthenticationManager(authenticationManager);
authenticationBuilder.authenticationEventPublisher(eventPublisher);
Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
sharedObjects);
if (!disableDefaults) {
// @formatter:off
// 追加各種SecurityConfigurer的具體實現類到httpSecurity中
http
.csrf().and()
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
.sessionManagement().and()
.securityContext().and()
.requestCache().and()
.anonymous().and()
.servletApi().and()
.apply(new DefaultLoginPageConfigurer<>()).and()
.logout();
ClassLoader classLoader = this.context.getClassLoader();
List<AbstractHttpConfigurer> defaultHttpConfigurers =
SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
http.apply(configurer);
}
}
//最終調用我們的繼承的WebSecurityConfigurerAdapter中重寫的configure(),將我們業務相關的權限配置規則信息進行初始化操作
configure(http);
return http;
}
protected AuthenticationManager authenticationManager() throws Exception {
if (!authenticationManagerInitialized) {
configure(localConfigureAuthenticationBldr);
if (disableLocalConfigureAuthenticationBldr) {
authenticationManager = authenticationConfiguration
.getAuthenticationManager();
}
else {
authenticationManager = localConfigureAuthenticationBldr.build();
}
authenticationManagerInitialized = true;
}
return authenticationManager;
}
}
2.2.1.2 configure方法
configure方法最終也調用到會我們繼承的WebSecurityConfigurerAdapter中重寫的configure(WebSecurity web)方法,默認實現中這個是一個空方法,具體應用中也經常重寫這個方法來實現特定需求。
2.2.1.3 peformBuild方法
具體的實現邏輯在WebSecurity類中
遍歷securityFilterChainBuilders屬性中的SecurityBuilder對象,並調用他的build方法。
這個securityFilterChainBuilders屬性我們前面也有提到過(具體請看2.2.1.1 init方法),就是在WebSecurityConfigurerAdapter類的init方法中獲取http後賦值給了WebSecurity。因此這個地方就是調用httpSecurity的build方法。
httpSecurity的build方式向其中追加一個個過濾器
@Override
protected Filter performBuild() throws Exception {
Assert.state(
!securityFilterChainBuilders.isEmpty(),
"At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke "
+ WebSecurity.class.getSimpleName()
+ ".addSecurityFilterChainBuilder directly");
int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
chainSize);
for (RequestMatcher ignoredRequest : ignoredRequests) {
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
}
// 遍歷securityFilterChainBuilders屬性中的SecurityBuilder對象,並調用他的build方法。
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
// 構建攔截器鏈
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if (httpFirewall != null) {
filterChainProxy.setFirewall(httpFirewall);
}
filterChainProxy.afterPropertiesSet();
Filter result = filterChainProxy;
if (debugEnabled) {
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);
}
postBuildAction.run();
return result;
}
2.2.2 Configurer轉換爲filter
在peformBuild方法,會在webSecurity類調用httpSecurity的build(具體請看2.2.1.3 peformBuild方法)
httpSecurity也繼承於SecurityBuilder,因此也會走build過程,init->configure->peformBuil
在init的時候我們可以看到,會執行這些配置信息,這是在2.2.1.1 init方法中注入的,
在configure方法中,會調用所有的configures,調用該類中的configure方法初始化filter類
@SuppressWarnings("unchecked")
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);
}
}
以CsfConfigurer爲例
public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
extends AbstractHttpConfigurer<CsrfConfigurer<H>, H> {
@SuppressWarnings("unchecked")
@Override
public void configure(H http) throws Exception {
// 創建Filter並配置
CsrfFilter filter = new CsrfFilter(this.csrfTokenRepository);
RequestMatcher requireCsrfProtectionMatcher = getRequireCsrfProtectionMatcher();
if (requireCsrfProtectionMatcher != null) {
filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
}
AccessDeniedHandler accessDeniedHandler = createAccessDeniedHandler(http);
if (accessDeniedHandler != null) {
filter.setAccessDeniedHandler(accessDeniedHandler);
}
LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
if (logoutConfigurer != null) {
logoutConfigurer
.addLogoutHandler(new CsrfLogoutHandler(this.csrfTokenRepository));
}
SessionManagementConfigurer<H> sessionConfigurer = http
.getConfigurer(SessionManagementConfigurer.class);
if (sessionConfigurer != null) {
sessionConfigurer.addSessionAuthenticationStrategy(
new CsrfAuthenticationStrategy(this.csrfTokenRepository));
}
filter = postProcess(filter);
http.addFilter(filter);
}
}
最後再看下HttpSecurity類執行build的最後一步 performBuild,這個方法就是在HttpSecurity中實現的
@Override
protected DefaultSecurityFilterChain performBuild() throws Exception {
Collections.sort(filters, comparator);
return new DefaultSecurityFilterChain(requestMatcher, filters);
}
可以看到,這個類只是把我們追加到HttpSecurity中的security進行了排序,用的排序類是FilterComparator,從而保證我們的filter按照正確的順序執行。接着將filters構建成filterChian返回。在前面WebSecurity的performBuild方法中,這個返回值會被包裝成FilterChainProxy,並作爲WebSecurity的build方法的放回值。從而以springSecurityFilterChain這個名稱註冊到springContext中(在WebSecurityConfiguration中做的)
ExpressionUrlAuthorizationConfigurer的繼承關係
ExpressionUrlAuthorizationConfigurer->AbstractInterceptUrlConfigurer->AbstractHttpConfigurer->SecurityConfigurerAdapter->SecurityConfigurer
對應的init方法在SecurityConfigurerAdapter類中,是個空實現,什麼也沒有做,configure方法在SecurityConfigurerAdapter類中也有一個空實現,在AbstractInterceptUrlConfigurer類中進行了重寫
ExpressionUrlAuthorizationConfigurer類圖如下所示: