說明
使用spring-boot 我們引入security的包 就可以自動實現簡單的登錄,是怎么做到的呢?
知道spring-security源碼,我們的可以通過打斷點方式,找到各個核心源碼處,知道各個配置原理,和擴展點 完成業務定制化邏輯
security自動化配置
1.在spring-boot-autoconfigure的spring.factories引入了security的自動化配置。我們主要看最核心的org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
spriing自動化配置原理可以參考《Spring Boot-Starter(九)》
2.SecurityAutoConfiguration實現
Import導入原理可以參考《spring源碼閱讀(五)-Spring Import注解使用》《Spring源碼閱讀(六)-ConfigurationClassPostProcessor》
@Configuration( proxyBeanMethods = false ) @ConditionalOnClass({DefaultAuthenticationEventPublisher.class})//class path有此類加載 @EnableConfigurationProperties({SecurityProperties.class}) //Import導入 我們主要看WebSecurityEnablerConfiguration @Import({SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class, SecurityDataConfiguration.class}) public class SecurityAutoConfiguration { public SecurityAutoConfiguration() { } @Bean @ConditionalOnMissingBean({AuthenticationEventPublisher.class})//容器沒有這個bean觸發加載 public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) { return new DefaultAuthenticationEventPublisher(publisher); } }
3.我們繼續看EnableWebSecurity
@Configuration( proxyBeanMethods = false//不需要代理 ) @ConditionalOnBean({WebSecurityConfigurerAdapter.class})//容器中有WebSecurityConfigurerAdapter對象觸發自動加載 @ConditionalOnMissingBean(//容器中不能出現springSecurityFilterChain的實例 name = {"springSecurityFilterChain"} ) @ConditionalOnWebApplication( type = ConditionalOnWebApplication.Type.SERVLET ) @EnableWebSecurity//組合注解 public class WebSecurityEnablerConfiguration { public WebSecurityEnablerConfiguration() { } }
4.
這里又用到了Import導入 我們主要看WebSecurityConfiguration
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class, HttpSecurityConfiguration.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; }
5.WebSecurityConfiguration首先會初始化一個webSecurity管理我們定義的WebSecurityConfigurerAdapter子類
org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration#setFilterChainProxySecurityConfigurer
/** *autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers() * 這個類里面就是根據beanFactory獲取WebSecurityConfigurer的實現 也就是我們的配置的WebSecurityConfigurerAdapter子類 */ @Autowired(required = false) public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor, @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers) throws Exception { //這里是通過new創建WebSecurity 同時通過objectPostProcessor 從容器中找如果有的話就依賴注入 //我們可以閱讀里面成員變量原理 通過容器注入對應對象完成初始化復制 this.webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor)); if (this.debugEnabled != null) { this.webSecurity.debug(this.debugEnabled); } //排序 webSecurityConfigurers.sort(WebSecurityConfiguration.AnnotationAwareOrderComparator.INSTANCE); Integer previousOrder = null; Object previousConfig = null; for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) { Integer order = WebSecurityConfiguration.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; } //循環遍歷設置到 add 到webSecurity成員變量configurers webSecurityConfigures為我們自定義繼承的WebSecurityConfigureAdapter配置類 for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) { this.webSecurity.apply(webSecurityConfigurer); } this.webSecurityConfigurers = webSecurityConfigurers; }
6.
public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain"; 最后通過org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration 注入到Servlet
org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration#springSecurityFilterChain
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) public Filter springSecurityFilterChain() throws Exception { boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty(); boolean hasFilterChain = !this.securityFilterChains.isEmpty(); Assert.state(!(hasConfigurers && hasFilterChain), "Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one."); //我們沒有配置 這里應該是配置一個默認的 if (!hasConfigurers && !hasFilterChain) { WebSecurityConfigurerAdapter adapter = this.objectObjectPostProcessor .postProcess(new WebSecurityConfigurerAdapter() { }); this.webSecurity.apply(adapter); } /** * securityFilterChains 是通過容器獲取 通過@Autowired set方法注入 * 這里也是一個擴展點 我們可以增加手動增加securityFilterChain */ for (SecurityFilterChain securityFilterChain : this.securityFilterChains) { this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain); for (Filter filter : securityFilterChain.getFilters()) { //如果這個filter是FilterSecurityInterceptor 則加入到securityInterceptor if (filter instanceof FilterSecurityInterceptor) { this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter); break; } } } /** * webSecurityCustomizers也是從容器獲取 * 也是一個擴展點。我們可以自定義 在build前對webSecurity做一些定制化操作 * @通過@Autowired set方法注入 */ for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) { customizer.customize(this.webSecurity); } /* *<1>執行build */ return this.webSecurity.build(); }
<1>
模板模式
org.springframework.security.config.annotation.AbstractSecurityBuilder#build
@Override public final O build() throws Exception { if (this.building.compareAndSet(false, true)) { //<2>模板模式 this.object = doBuild(); return this.object; } throw new AlreadyBuiltException("This object has already been built"); }
<2>
所有配置都繼承這個類
org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#doBuild
/** * @return * @throws Exception */ @Override protected final O doBuild() throws Exception { synchronized (this.configurers) { this.buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING; //空實現 beforeInit(); //<3>初始化config init(); this.buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING; beforeConfigure(); //<4> configure(); this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING;
//模板方法 抽象的子類必須實現 真正的build方法 O result = performBuild(); this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT; return result; } }
針對不同的build會調用不同的performBuild方法
如webSecurity則是調用<10>
如HttpSecurity則調用<13>
針對DefaultPasswordEncoderAuthenticationManagerBuilder 則調用<16>
<3>
org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#init
@SuppressWarnings("unchecked") private void init() throws Exception { //獲得configures調用configures的init 注意不同的配置類 就是調用不同配置的init方法 Collection<SecurityConfigurer<O, B>> configurers = getConfigurers(); for (SecurityConfigurer<O, B> configurer : configurers) { configurer.init((B) this); } for (SecurityConfigurer<O, B> configurer : this.configurersAddedInInitializing) { configurer.init((B) this); } }
針對不同配置 Configurers不一樣,如果是WebSecurity則getConfigures是 WebSecurityConfigurerAdapter 所以調用的WebSecurityConfigurerAdapter的init方法<6>
<4>
org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder#configure
@SuppressWarnings("unchecked") private void configure() throws Exception { Collection<SecurityConfigurer<O, B>> configurers = getConfigurers(); for (SecurityConfigurer<O, B> configurer : configurers) { //不同的配置類就是調用不同的 configure方法初始化 configurer.configure((B) this); } }
針對不同配置 Configurers不一樣
1.如果是WebSecurity則getConfigures是 WebSecurityConfigurerAdapter 所以調用的WebSecurityConfigurerAdapter的configure方法<5>
2.如果是HttpSecurity則是<11>處配置的各種config如 如果有需要可以研究各個config如何初始化的比如我們參考HeaderConfigure的實現<12>
3.針對DefaultPasswordEncoderAuthenticationManagerBuilder 的confgure請看<15>
<15>
<5>
一般我們都會重寫
@Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/js/**", "/css/**", "/images/**"); }
<6>
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#init
@Override public void init(WebSecurity web) throws Exception { //<7>初始化HttpSecurity 本事也是一個build HttpSecurity http = getHttp(); //將HttpSecurity add 到WebSecurity 后續會使用securityFilterChainBuilders web.addSecurityFilterChainBuilder(http).postBuildAction(() -> { FilterSecurityInterceptor securityInterceptor = http.getSharedObject(FilterSecurityInterceptor.class); web.securityInterceptor(securityInterceptor); }); }
<7>
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#getHttp
@SuppressWarnings({ "rawtypes", "unchecked" }) protected final HttpSecurity getHttp() throws Exception { if (this.http != null) { return this.http; } //從容器獲取AuthenticationEventPublisher AuthenticationEventPublisher eventPublisher = getAuthenticationEventPublisher(); //設置到localConfigureAuthenticationBldr build this.localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher); //<8>通過localConfigureAuthenticationBldr build初始化AuthenticationManager 這里是org.springframework.security.authentication.ProviderManager AuthenticationManager authenticationManager = authenticationManager(); //給authenticationBuilder 設置authenticationManager this.authenticationBuilder.parentAuthenticationManager(authenticationManager); Map<Class<?>, Object> sharedObjects = createSharedObjects(); //初始化HttpSecurity this.http = new HttpSecurity(this.objectPostProcessor, this.authenticationBuilder, sharedObjects); if (!this.disableDefaults) { //<11>進行默認配置 applyDefaultConfiguration(this.http); ClassLoader classLoader = this.context.getClassLoader(); List<AbstractHttpConfigurer> defaultHttpConfigurers = SpringFactoriesLoader .loadFactories(AbstractHttpConfigurer.class, classLoader); for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) { this.http.apply(configurer); } } /** * <9>傳入我們的http build 讓我們可以做定制化配置 * @Override * protected void configure(HttpSecurity http) throws Exception{ * .... * } */ configure(this.http); return this.http; }
<8>
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#authenticationManager
protected AuthenticationManager authenticationManager() throws Exception { //避免重復初始化 if (!this.authenticationManagerInitialized) { /** * 這里就是調用自定義繼承WebSecurityConfigurerAdapter重寫的configure(AuthenticationManagerBuilder auth) * 傳入build 讓我們可以自定義一些參數配置 比如配置用戶信息是基於應用 還是內存 * auth.inMemoryAuthentication() * .withUser("liqiang").password("liqiang").roles("admin") * .and() * .withUser("admin").password("admin").roles("admin"); */ configure(this.localConfigureAuthenticationBldr); // if (this.disableLocalConfigureAuthenticationBldr) { //這里是一個擴展點我們可以直接 authenticationConfiguration是根據spring容器初始化的 根據authenticationConfiguration而不是通過build this.authenticationManager = this.authenticationConfiguration.getAuthenticationManager(); } else { //正常是走得這個build方法 build authenticationManager 這里會調用<1> 為何到1請看下面說明 //默認 localConfigureAuthenticationBldr是DefaultPasswordEncoderAuthenticationManagerBuilder //初始化處 詳看:<14> this.authenticationManager = this.localConfigureAuthenticationBldr.build(); } this.authenticationManagerInitialized = true; } return this.authenticationManager; }
configure(this.localConfigureAuthenticationBldr);
這里需要強調的一點是調用我們繼承的WebSecurityConfigurerAdapter 我們可以定義用戶管理器
如基於應用內存
內部創建inMemoryAuthentication方法 創建InMemoryUserDetailsManagerConfigurer 到build confgures
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { /** * inMemoryAuthentication 開啟在內存中定義用戶 * 多個用戶通過and隔開 */ auth.inMemoryAuthentication() .withUser("liqiang").password("liqiang").roles("admin") .and() .withUser("admin").password("admin").roles("admin"); }
自定義userDetail
內部創建DaoAuthenticationConfigurer 到build confgures
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { /** * inMemoryAuthentication 開啟在內存中定義用戶 * 多個用戶通過and隔開 */ auth.userDetailsService(new UserDetailsService() { @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { return null; } }); }
基於封裝的jdbc查詢jdbcAuthentication方法 創建JdbcUserDetailsManagerConfigurer 到build confgures
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { /** * inMemoryAuthentication 開啟在內存中定義用戶 * 多個用戶通過and隔開 */ auth.jdbcAuthentication().dataSource(null).usersByUsernameQuery(""); }
他們本質都是根據auth.創建不同的config對象 設置到build的configures
<9>
/** * 對於不需要授權的靜態文件放行 * @param web * @throws Exception */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/js/**", "/css/**", "/images/**"); }
<10>
org.springframework.security.config.annotation.web.builders.WebSecurity#performBuild
securityFilterChains為什么是列表
因為不同的url可以有不同的處理邏輯
@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 " + "or by adding a @Configuration that extends WebSecurityConfigurerAdapter. " + "More advanced users can invoke " + WebSecurity.class.getSimpleName() + ".addSecurityFilterChainBuilder directly"); /** * 得我們設置的忽略檢查為他們添加一個 這里會添加3個chains 根據匹配做不通過處理 * public void configure(WebSecurity web) throws Exception { * web.ignoring().antMatchers("/js/**", "/css/**", "/images/**"); * } */ int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size(); List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize); for (RequestMatcher ignoredRequest : this.ignoredRequests) { securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest)); } //securityFilterChainBuilders為HttpSecurity<6>處初始化 for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) { //執行build<1> 最終會構建成<13> securityFilterChains.add(securityFilterChainBuilder.build()); } //通過FilterChainProxy 代理管理 它實現了ServletFilter 通過FilterChainProxy為Servlet入口 進入security的自己的filter FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains); if (this.httpFirewall != null) { filterChainProxy.setFirewall(this.httpFirewall); } if (this.requestRejectedHandler != null) { filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler); } filterChainProxy.afterPropertiesSet(); Filter result = filterChainProxy; if (this.debugEnabled) { result = new DebugFilter(filterChainProxy); } this.postBuildAction.run(); //返回filter 我們請求都會到filterChainProxy 通過他調用security的filter實現securityfilter 注入邏輯 return result; }
<11>
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#applyDefaultConfiguration
private void applyDefaultConfiguration(HttpSecurity http) throws Exception { //http本質也是build 這里都是配置默認的config configure add CsrfConfigurer http.csrf(); //默認增加一個WebAsyncManagerIntegrationFilter http.addFilter(new WebAsyncManagerIntegrationFilter()); //configures add ExceptionHandlingConfigurer http.exceptionHandling(); //configures add HeadersConfigurer http.headers(); //configures add SessionManagementConfigurer http.sessionManagement(); //configure add SecurityContextConfigurer http.securityContext(); //configure add RequestCacheConfigurer http.requestCache(); ///configure add AnonymousConfigurer http.anonymous(); ///configure add ServletApiConfigurer http.servletApi(); //自定義默認config http.apply(new DefaultLoginPageConfigurer<>()); //configure LogoutConfigurer http.logout(); }
<12>
org.springframework.security.config.annotation.web.configurers.HeadersConfigurer#configure
@Override public void configure(H http) { //創建一個HeaderFilter HeaderWriterFilter headersFilter = createHeaderWriterFilter(); //添加到HttpSecurityFilter http.addFilter(headersFilter); }
<13>
org.springframework.security.config.annotation.web.builders.HttpSecurity#performBuild
@Override protected DefaultSecurityFilterChain performBuild() { //將httpSecurity filter排序 this.filters.sort(OrderComparator.INSTANCE); List<Filter> sortedFilters = new ArrayList<>(this.filters.size()); for (Filter filter : this.filters) { sortedFilters.add(((OrderedFilter) filter).filter); } //requestMatcher 為匹配條件 DefaultSecurityFilterChain 包裝起來 管理所有Filter return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters); }
<14>
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#setApplicationContext
@Autowired public void setApplicationContext(ApplicationContext context) { this.context = context; ObjectPostProcessor<Object> objectPostProcessor = context.getBean(ObjectPostProcessor.class); LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(context); this.authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder); this.localConfigureAuthenticationBldr = new DefaultPasswordEncoderAuthenticationManagerBuilder( objectPostProcessor, passwordEncoder) { @Override public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) { WebSecurityConfigurerAdapter.this.authenticationBuilder.eraseCredentials(eraseCredentials); return super.eraseCredentials(eraseCredentials); } @Override public AuthenticationManagerBuilder authenticationEventPublisher( AuthenticationEventPublisher eventPublisher) { WebSecurityConfigurerAdapter.this.authenticationBuilder.authenticationEventPublisher(eventPublisher); return super.authenticationEventPublisher(eventPublisher); } }; }
<15>
跟WebSecurityConfigurerAdapter 一樣 以下3個方法都是add不同的config 執行不同的初始化邏輯
static class DefaultPasswordEncoderAuthenticationManagerBuilder extends AuthenticationManagerBuilder { private PasswordEncoder defaultPasswordEncoder; /** * Creates a new instance * @param objectPostProcessor the {@link ObjectPostProcessor} instance to use. */ DefaultPasswordEncoderAuthenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor, PasswordEncoder defaultPasswordEncoder) { super(objectPostProcessor); this.defaultPasswordEncoder = defaultPasswordEncoder; } @Override public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication() throws Exception { return super.inMemoryAuthentication().passwordEncoder(this.defaultPasswordEncoder); } @Override public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication() throws Exception { return super.jdbcAuthentication().passwordEncoder(this.defaultPasswordEncoder); } @Override public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService( T userDetailsService) throws Exception { return super.userDetailsService(userDetailsService).passwordEncoder(this.defaultPasswordEncoder); } }
<16>
org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder#performBuild
@Override protected ProviderManager performBuild() throws Exception { if (!isConfigured()) { this.logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null."); return null; } //通過ProviderManager 統一管理providers authenticationProviders 都可以定制 ProviderManager providerManager = new ProviderManager(this.authenticationProviders, this.parentAuthenticationManager); if (this.eraseCredentials != null) { providerManager.setEraseCredentialsAfterAuthentication(this.eraseCredentials); } if (this.eventPublisher != null) { providerManager.setAuthenticationEventPublisher(this.eventPublisher); } //依賴注入 providerManager = postProcess(providerManager); return providerManager; }