前言
我們知道Spring Security的核心實現原理都是從filter開始的,Spring Security通過構造層層filter來實現登錄跳轉、權限驗證,角色管理等功能。本章通過剖析Spring Security的核心源碼來說明Spring Security的filter是如何開始構造並運行的。
從最初開始
往往我們定義一個Spring Security程序都是通過配置一個WebSecurityConfig類開始的,簡單代碼如下:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and().formLogin();
}
}
通過以上代碼一個簡單的Spring Security應用程序就能成功執行了,該程序能攔截除了/login路徑以外的所有請求到登錄頁面。
我們可以看到以上代碼並沒有任何顯示聲明filter的語句,那麼Spring Security是如何通過上述代碼生成filter的呢?下面就由我來一層層解剖Spring Security的源碼來說明。
@EnableWebSecurity註解
我們注意到如上代碼有個@EnableWebSecurity註解,進入該註解查看
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
#注意這裏!
@Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class})
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
boolean debug() default false;
}
WebSecurityConfiguration類
我們可以看到如上該註解導入了WebSecurityConfiguration類,進入該類查看:
@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;
public WebSecurityConfiguration() {
}
@Bean
public static DelegatingApplicationListener delegatingApplicationListener() {
return new DelegatingApplicationListener();
}
........
}
WebSecurityConfiguration類是作爲一個Spring配置源,同時定義了許多bean,這裏重點看如下這個方法:
@Autowired(
required = false
)
public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor,
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}")
List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers) throws Exception {
// 這段代碼初始化webSecurity
this.webSecurity = (WebSecurity)objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
if (this.debugEnabled != null) {
this.webSecurity.debug(this.debugEnabled);
}
// webSecurityConfigurers該屬性是通過@Value註解注入的
Collections.sort(webSecurityConfigurers, WebSecurityConfiguration.AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
Object previousConfig = null;
Iterator var5;
SecurityConfigurer config;
for(var5 = webSecurityConfigurers.iterator(); var5.hasNext(); previousConfig = config) {
config = (SecurityConfigurer)var5.next();
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;
}
// 將webSecurityConfigurers依次放入webSecurity
var5 = webSecurityConfigurers.iterator();
while(var5.hasNext()) {
config = (SecurityConfigurer)var5.next();
this.webSecurity.apply(config);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
總結下該方法所做的主要操作:
- 首先初始化了webSecurity屬性,該屬性對應WebSecurity類
- 注入了webSecurityConfigurers屬性,該屬性是一個List<SecurityConfigurer>集合
- 遍歷webSecurityConfigurers集合,調用webSecurity的apply方法,該方法參數爲SecurityConfigurer接口
這裏有一個重要的接口SecurityConfigurer接口,該接口代碼如下:
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
void init(B var1) throws Exception;
void configure(B var1) throws Exception;
}
回顧上面我們編寫的WebSecurityConfig配置類,也有一個configure方法,那麼我們猜測WebSecurityConfig類是不是也實現了SecurityConfigurer接口呢?答案是是的,我們可以看WebSecurityConfig類的類圖
可以看到WebSecurityConfig類實現了SecurityConfigurer接口。
因此webSecurityConfigurers屬性通過依賴注入包含了WebSecurityConfig類,通過上述第3條操作將我們配置的WebSecurityConfig類和WebSecurity類關聯起來。
WebSecurity類
到這裏我們知道了WebSecurityConfiguration類調用上述方法將我們配置的WebSecurityConfig類用WebSecurity類的apply方法關聯起來,那麼我們詳細看看WebSecurity類的apply方法:
public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer) throws Exception {
configurer.addObjectPostProcessor(this.objectPostProcessor);
configurer.setBuilder(this);
// 繼續調用該類的add方法
this.add(configurer);
return configurer;
}
private <C extends SecurityConfigurer<O, B>> void add(C configurer) throws Exception {
Assert.notNull(configurer, "configurer cannot be null");
// 獲取class屬性
Class<? extends SecurityConfigurer<O, B>> clazz = configurer.getClass();
// 獲取LinkedHashMap
LinkedHashMap var3 = this.configurers;
synchronized(this.configurers) {
if (this.buildState.isConfigured()) {
throw new IllegalStateException("Cannot apply " + configurer + " to already built object");
} else {
List<SecurityConfigurer<O, B>> configs = this.allowConfigurersOfSameType ? (List)this.configurers.get(clazz) : null;
if (configs == null) {
configs = new ArrayList(1);
}
((List)configs).add(configurer);
// 將configurer放入一個LinkedHashMap中
this.configurers.put(clazz, configs);
if (this.buildState.isInitializing()) {
this.configurersAddedInInitializing.add(configurer);
}
}
}
}
從上述代碼可知,實際上就是將WebSecurityConfig類放入了WebSecurity類的一個LinkedHashMap中,該LinkedHashMap在WebSecurity中屬性名爲configurers。
我們繼續回到WebSecurityConfiguration類,查看它的另外一個重要的方法:
@Bean(
name = {"springSecurityFilterChain"}
)
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
if (!hasConfigurers) {
WebSecurityConfigurerAdapter adapter = (WebSecurityConfigurerAdapter)this.objectObjectPostProcessor.postProcess(new WebSecurityConfigurerAdapter() {
});
this.webSecurity.apply(adapter);
}
return (Filter)this.webSecurity.build();
}
該方法即爲Spring Security構建Filter的核心方法,通過webSecurity的build方法構建了Spring Security的Filter。
我們繼續查看WebSecurity類的build方法:
public final O build() throws Exception {
if (this.building.compareAndSet(false, true)) {
this.object = this.doBuild();
return this.object;
} else {
throw new AlreadyBuiltException("This object has already been built");
}
}
實際上調用了上層的doBuild:
protected final O doBuild() throws Exception {
LinkedHashMap var1 = this.configurers;
synchronized(this.configurers) {
this.buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING;
this.beforeInit();
this.init();
this.buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING;
this.beforeConfigure();
this.configure();
this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING;
O result = this.performBuild();
this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT;
return result;
}
}
這裏主要看WebSecurity的init方法和performBuild方法,首先看init方法
private void init() throws Exception {
// this.getConfigurers()該方法實際上獲取WebSecurity中LinkedHashMap中的Value值集合
Collection<SecurityConfigurer<O, B>> configurers = this.getConfigurers();
Iterator var2 = configurers.iterator();
SecurityConfigurer configurer;
while(var2.hasNext()) {
configurer = (SecurityConfigurer)var2.next();
// 調用SecurityConfigurer的init方法
configurer.init(this);
}
var2 = this.configurersAddedInInitializing.iterator();
while(var2.hasNext()) {
configurer = (SecurityConfigurer)var2.next();
configurer.init(this);
}
}
通過該代碼可知,該方法首先獲取WebSecurity中的LinkedHashMap中的Value值集合,再對Value值進行遍歷並執行其中的init方法,從上面的代碼分析我們知道WebSecurity中的LinkedHashMap實際存的就是WebSecurityConfig,這段代碼將會調用WebSecurityConfig的init方法,而WebSecurityConfig的init方法來自於它的父類WebSecurityConfigurerAdapter,該init方法代碼如下:
public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
public void init(final WebSecurity web) throws Exception {
// 獲取HttpSecurity
final HttpSecurity http = this.getHttp();
// 將HttpSecurity放入WebSecurity中
web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
public void run() {
FilterSecurityInterceptor securityInterceptor =
(FilterSecurityInterceptor)http.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
}
});
}
protected final HttpSecurity getHttp() throws Exception {
if (this.http != null) {
return this.http;
} else {
DefaultAuthenticationEventPublisher eventPublisher = (DefaultAuthenticationEventPublisher)this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
this.localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
AuthenticationManager authenticationManager = this.authenticationManager();
this.authenticationBuilder.parentAuthenticationManager(authenticationManager);
this.authenticationBuilder.authenticationEventPublisher(eventPublisher);
Map<Class<? extends Object>, Object> sharedObjects = this.createSharedObjects();
this.http = new HttpSecurity(this.objectPostProcessor, this.authenticationBuilder, sharedObjects);
if (!this.disableDefaults) {
((HttpSecurity)((DefaultLoginPageConfigurer)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)this.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);
Iterator var6 = defaultHttpConfigurers.iterator();
while(var6.hasNext()) {
AbstractHttpConfigurer configurer = (AbstractHttpConfigurer)var6.next();
this.http.apply(configurer);
}
}
// 調用本類的configure方法
this.configure(this.http);
return this.http;
}
}
// 模板方法設計模式,子類WebSecurityConfig將會覆蓋該方法
protected void configure(HttpSecurity http) throws Exception {
this.logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
((HttpSecurity)((HttpSecurity)((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated().and()).formLogin().and()).httpBasic();
}
}
以上代碼最終還是實際調用了我們寫的WebSecurityConfig類的configure方法。
仔細觀察以上代碼,我們發現有一條語句web.addSecurityFilterChainBuilder(http),該語句將構建的HttpSecurity放入WebSecurity類中,以下是該方法源碼:
public WebSecurity addSecurityFilterChainBuilder(SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
this.securityFilterChainBuilders.add(securityFilterChainBuilder);
return this;
}
實際上就是將HttpSecurity放入了WebSecurity的一個list集合裏,該list集合屬性名爲securityFilterChainBuilders。
到目前爲止,我們終於知道我們編寫的WebSecurityConfig類的configure方法是如何被調用的了,但是仍有許多疑問沒解開,比如HttpSecurity類的作用,Spring Security是如何通過HttpSecurity類構建一條攔截器鏈等。
這裏我們先不分析HttpSecurity類的具體實現,再來看看WebSecurity的init方法執行後所執行的performBuild方法,該方法源碼如下:
protected Filter performBuild() throws Exception {
Assert.state(!this.securityFilterChainBuilders.isEmpty(), () -> {
return "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 = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList(chainSize);
Iterator var3 = this.ignoredRequests.iterator();
while(var3.hasNext()) {
RequestMatcher ignoredRequest = (RequestMatcher)var3.next();
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest, new Filter[0]));
}
/ 遍歷securityFilterChainBuilders集合
var3 = this.securityFilterChainBuilders.iterator();
while(var3.hasNext()) {
SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder = (SecurityBuilder)var3.next();
// 執行securityFilterChainBuilders集合單位的build方法,返回一個SecurityFilterChain類,並加入List<SecurityFilterChain>中
securityFilterChains.add(securityFilterChainBuilder.build());
}
// 將List<SecurityFilterChain>類構建成一個FilterChainProxy代理類
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if (this.httpFirewall != null) {
filterChainProxy.setFirewall(this.httpFirewall);
}
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();
// 返回FilterChainProxy代理類
return (Filter)result;
}
該方法執行的操作主要如下:
- 遍歷securityFilterChainBuilders集合,並執行其中的build方法,從上面代碼分析可知,securityFilterChainBuilders集合裏存儲了HttpSecurity,所以這裏執行了HttpSecurity的build方法構建SecurityFilterChain類
- 將List<SecurityFilterChain>集合構建成一個FilterChainProxy代理類
- 返回這個FilterChainProxy代理類
到這裏總的過程就非常明瞭了,實際上Spring Security的頂層filter就是一個FilterChainProxy類,而HttpSecurity主要用於註冊和實例化各種filter
到這裏就分成了兩路,一路是HttpSecurity的build方法構建SecurityFilterChain類的原理,一路是FilterChainProxy類的作用,我們先從FilterChainProxy類開始
FilterChainProxy類
當請求到達的時候,FilterChainProxy會調用dofilter()方法,會遍歷所有的SecurityFilterChain,對匹配到的url,則一一調用SecurityFilterChain中的filter做認證授權。FilterChainProxy的dofilter()中調用了doFilterInternal()方法,如下:
private void doFilterInternal(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = firewall
.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse fwResponse = firewall
.getFirewalledResponse((HttpServletResponse) response);
// 獲取請求對應的filter列表
List<Filter> filters = getFilters(fwRequest);
if (filters == null || filters.size() == 0) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(fwRequest)
+ (filters == null ? " has no matching filters"
: " has an empty filter list"));
}
fwRequest.reset();
chain.doFilter(fwRequest, fwResponse);
return;
}
VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
// 執行每個filter
vfc.doFilter(fwRequest, fwResponse);
}
// 通過遍歷filterChains,調用SecurityFilterChain的matches方法,判斷當前的請求對應哪些filter,返回匹配的filter列表
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
我們理清了FilterChainProxy類的作用,那麼這些SecurityFilterChain是從哪來的呢?從上節可知SecurityFilterChain是由HttpSecurity的build方法生成的,下面我們分析下HttpSecurity類
HttpSecurity
HttpSecurity與WebSecurity一樣,都繼承了AbstractConfiguredSecurityBuilder類,而WebSecurity的build和doBuild方法和LinkedHashMap屬性,均來自AbstractConfiguredSecurityBuilder,故HttpSecurity的build方法代碼與WebSecurity的相同,區別在於LinkedHashMap存儲的東西不同,HttpSecurity正是通過如此來生成SecurityFilterChain類的。
下面我們來看HttpSecurity構建filter的幾個常見方法:
public ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests() throws Exception {
ApplicationContext context = this.getContext();
return ((ExpressionUrlAuthorizationConfigurer)this.getOrApply(new ExpressionUrlAuthorizationConfigurer(context))).getRegistry();
}
public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
return (FormLoginConfigurer)this.getOrApply(new FormLoginConfigurer());
}
都調用了getOrApply方法,再來看getOrApply方法,又調用了其中的apply方法
private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(C configurer) throws Exception {
C existingConfig = (SecurityConfigurerAdapter)this.getConfigurer(configurer.getClass());
return existingConfig != null ? existingConfig : this.apply(configurer);
}
public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer) throws Exception {
configurer.addObjectPostProcessor(this.objectPostProcessor);
configurer.setBuilder(this);
this.add(configurer);
return configurer;
}
apply方法又調用了add方法,這裏的add方法最終還是將該configurer加入了linkedHashMap中
private <C extends SecurityConfigurer<O, B>> void add(C configurer) throws Exception {
Assert.notNull(configurer, "configurer cannot be null");
Class<? extends SecurityConfigurer<O, B>> clazz = configurer.getClass();
LinkedHashMap var3 = this.configurers;
synchronized(this.configurers) {
if (this.buildState.isConfigured()) {
throw new IllegalStateException("Cannot apply " + configurer + " to already built object");
} else {
List<SecurityConfigurer<O, B>> configs = this.allowConfigurersOfSameType ? (List)this.configurers.get(clazz) : null;
if (configs == null) {
configs = new ArrayList(1);
}
((List)configs).add(configurer);
this.configurers.put(clazz, configs);
if (this.buildState.isInitializing()) {
this.configurersAddedInInitializing.add(configurer);
}
}
}
}
故HttpSecurity在構建filter的過程中,本質還是將形如ExpressionUrlAuthorizationConfigurer、FormLoginConfigurer等類加入了它的LinkedHashMap中。
那麼將這些Configurer類存入LinkedHashMap的作用又是什麼?我們回憶上面WebSecurity類的doBuild方法,我們知道HttpSecurity類調用的doBuild方法與WebSecurity類一樣,而通過觀察WebSecurity類doBuild方法裏this.init();this.configure();這些語句的具體實現,實際就是調用其LinkedHashMap中的元素的init方法和configure方法。
我們現在來查看其中一個ExpressionUrlAuthorizationConfigurer類的configure方法的詳細代碼:
public void configure(H http) throws Exception {
FilterInvocationSecurityMetadataSource metadataSource = this.createMetadataSource(http);
if (metadataSource != null) {
FilterSecurityInterceptor securityInterceptor = this.createFilterSecurityInterceptor(http, metadataSource, (AuthenticationManager)http.getSharedObject(AuthenticationManager.class));
if (this.filterSecurityInterceptorOncePerRequest != null) {
securityInterceptor.setObserveOncePerRequest(this.filterSecurityInterceptorOncePerRequest);
}
securityInterceptor = (FilterSecurityInterceptor)this.postProcess(securityInterceptor);
// 將Filter加入了HttpSecurity的Filters集合中
http.addFilter(securityInterceptor);
http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor);
}
}
最後來看看HttpSecruity的performBuild()方法:
protected DefaultSecurityFilterChain performBuild() throws Exception {
Collections.sort(this.filters, this.comparator);
return new DefaultSecurityFilterChain(this.requestMatcher, this.filters);
}
實際上就是通過Filters集合構建了SecurityFilterChain。
從上面代碼可總結出,HttpSecurity內部維護一個Filter列表,而HttpSecurity調用形如authorizeRequests(),formLogin()等方法實際上就是將filter添加入它的列表當中,最後通過performBuild()方法構建出SecurityFilterChain,至此HttpSecurity構建filter的總過程就完成了。
總結
到目前爲止,我們終於知道Spring Security是如何一步步的構建和初始化filter的了,我們最後再來簡單總結下構建過程:
- Spring Security啓動過程中通過WebSecurityConfiguration實例化WebSecurity
- WebSecurityConfiguration會將使用者編寫的WebSecurityConfig類放入WebSecurity中的LinkedHashMap中
- 在構建WebSecurity的時候,會調用WebSecurity的doBuild()方法,這個方法是一個核心方法。
- doBuild中的init方法將會調用LinkedHashMap中元素的init方法(這裏的元素是WebSecurityConfig),然後WebSecurityConfig的init方法會調用configure方法,調用configure方法後,將會初始化HttpSecurity構建各種Filter,這時HttpSecurity將會加入WebSecurity中。
- doBuild中的init方法調用完後將會調用下一個performBuild()方法,該方法會獲取到HttpSecurity調用其doBuild方法構造SecurityFilterChain
- 將獲取到的SecurityFilterChain構建成一個FilterChainProxy類,作爲Spring Security的頂層filter
- 至此Spring Security的Filter構建完成