核心思想
spring對請求的處理過程如下:
而security所有認(rèn)證邏輯作為特殊的一個Filter加入到spring處理servelet的過濾鏈中,即FilterChainProxy;
而這個FilterChainProxy內(nèi)部又有多個過濾器鏈FilterChain打却,每個鏈有matcher衷快,用于匹配請求帚桩,請求來時選擇最先匹配的一條過濾器鏈做權(quán)限認(rèn)證等龙,每條過濾器鏈又由多個多濾器Filter依序連接而成闽坡。如下圖:
編程范式
configuator配置類裝配到builder類中,builder類借助confuguator類構(gòu)造filter或者filterChain
SpringSecurity有兩個重要的builder:
- WebSecurity:構(gòu)造FilterChainProxy(由多條過濾器鏈SecurityFilterChain組成的代理)
- HttpSecurity:構(gòu)造過濾器鏈SecurityFilterChain(DefaultSecurityFilterChain)
Spring如何加載FilterChainProxy
首先生成FilterChainProxy實例迅脐,將FilterChainProxy實例再封裝到DelegatingFilterProxy(java web的標(biāo)準(zhǔn)過濾器)芍殖,作為一個web的Filter再注冊到spring上下文。
生成FilterChainProxy實例
- 通過SpringBootApplication注解加載EnableAutoConfiguration注解
@SpringBootApplication
public class AuthCodeServerApplication {
public static void main(String[] args) {
SpringApplication.run(AuthCodeServerApplication.class, args);
}
}
public @interface SpringBootApplication {
@AliasFor(
annotation = EnableAutoConfiguration.class,
attribute = "exclude"
)
......
- 自動加載SecurityAutoConfiguation配置類
在spring.factories文件中有
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
......
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
......
- 加載SpringBootWebSecurityConfiguration配置類
@Import({SpringBootWebSecurityConfiguration.class, AuthenticationManagerConfiguration.class, BootGlobalAuthenticationConfiguration.class, SecurityDataConfiguration.class})
public class SecurityAutoConfiguration {
......
- 加載EnableWebSecurity注解
@Configuration
@EnableConfigurationProperties
@ConditionalOnClass({EnableWebSecurity.class, AuthenticationEntryPoint.class})
@ConditionalOnMissingBean({WebSecurityConfiguration.class})
@ConditionalOnWebApplication
@EnableWebSecurity
public class SpringBootWebSecurityConfiguration {
......
- 加載WebSecurityConfiguation配置類
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class})
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
boolean debug() default false;
}
- 通過WebSecurityConfiguation配置類的springSecurityFilterChain方法 產(chǎn)生FilterChainProxy 實例(實例名為springSecurityFilterChain)
@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
private WebSecurity webSecurity;
private Boolean debugEnabled;
private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;
private ClassLoader beanClassLoader;
......
@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();
}
至于this.webSecurity.build()內(nèi)部怎么實現(xiàn)的仪际,后面再講围小。
將FilterChainProxy實例注冊到spring
將FilterChainProxy再封裝到DelegatingFilterProxy,在注入到spring上下文树碱。有兩種方式肯适,如下:
- 通過SecurityFilterAutoConfiguration配置類
@Bean
@ConditionalOnBean(
name = {"springSecurityFilterChain"}
)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(SecurityProperties securityProperties) {
DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean("springSecurityFilterChain", new ServletRegistrationBean[0]);
registration.setOrder(securityProperties.getFilterOrder());
registration.setDispatcherTypes(this.getDispatcherTypes(securityProperties));
return registration;
}
- 通過繼承實現(xiàn)AbstractSecurityWebApplicationInitializer抽象類
在onStartUp方法里調(diào)用insertSpringSecurityFilterChain方法注入
public final void onStartup(ServletContext servletContext) throws ServletException {
this.beforeSpringSecurityFilterChain(servletContext);
if (this.configurationClasses != null) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(this.configurationClasses);
servletContext.addListener(new ContextLoaderListener(rootAppContext));
}
if (this.enableHttpSessionEventPublisher()) {
servletContext.addListener("org.springframework.security.web.session.HttpSessionEventPublisher");
}
servletContext.setSessionTrackingModes(this.getSessionTrackingModes());
this.insertSpringSecurityFilterChain(servletContext);
this.afterSpringSecurityFilterChain(servletContext);
}
......
private void insertSpringSecurityFilterChain(ServletContext servletContext) {
String filterName = "springSecurityFilterChain";
DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(filterName);
String contextAttribute = this.getWebApplicationContextAttribute();
if (contextAttribute != null) {
springSecurityFilterChain.setContextAttribute(contextAttribute);
}
this.registerFilter(servletContext, true, filterName, springSecurityFilterChain);
}
詳解FilterChainProxy實例如何產(chǎn)生
上面講到了通過調(diào)用this.webSecurity.build()方法產(chǎn)生FilterChainProxy實例,現(xiàn)在仔細(xì)分析具體怎么實現(xiàn)的成榜。
- 實例化并配置WebSecurity
在WebSecurityConfiguration配置類框舔,通過setFilterChainProxySecurityConfigurer方法實例化WebSecurity,并搜索實現(xiàn)了WebSecurityConfigurer(WebSecurityConfigurerAdapter)的所有配置類赎婚,加載到WebSecurity中
@Autowired(
required = false
)
public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor, @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers) throws Exception {
this.webSecurity = (WebSecurity)objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
if (this.debugEnabled != null) {
this.webSecurity.debug(this.debugEnabled);
}
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;
}
var5 = webSecurityConfigurers.iterator();
while(var5.hasNext()) {
config = (SecurityConfigurer)var5.next();
this.webSecurity.apply(config);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
- 調(diào)用WebSucurity 的build方法構(gòu)造FilterChainProxy實例
見上面第5步
最終會調(diào)到doBuild方法刘绣。
protected final O doBuild() throws Exception {
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;
}
}
由上可見,主要是init挣输、configure纬凤、performBuild三個方法
2.1. init(WebSecurity)
會遍歷所有之前加載好的配置類configuator(adaptor),調(diào)用其init撩嚼。
private void init() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = this.getConfigurers();
Iterator var2 = configurers.iterator();
SecurityConfigurer configurer;
while(var2.hasNext()) {
configurer = (SecurityConfigurer)var2.next();
configurer.init(this);
}
var2 = this.configurersAddedInInitializing.iterator();
while(var2.hasNext()) {
configurer = (SecurityConfigurer)var2.next();
configurer.init(this);
}
}
其中配置類的init方法停士,主要是構(gòu)造了HttpSecurity,放入到securityFilterChainBuilders完丽;并在postBuild之后設(shè)置inteceptor到websecurity恋技。可見WebSecurityConfigurerAdapter類的init方法實現(xiàn):
public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = this.getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
public void run() {
FilterSecurityInterceptor securityInterceptor = (FilterSecurityInterceptor)http.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
}
});
}
2.2. configure(WebSecurity)
會遍歷所有之前加載好的配置類configuator(adaptor)逻族,調(diào)用其configure
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = this.getConfigurers();
Iterator var2 = configurers.iterator();
while(var2.hasNext()) {
SecurityConfigurer<O, B> configurer = (SecurityConfigurer)var2.next();
configurer.configure(this);
}
}
配置類的configure方法丢烘,主要是配置上一步構(gòu)造好的HttpSecurity實例卖词,將其相關(guān)的configuator配置類裝配到HttpSecurity實例校哎∏鲁可見WebSecurityConfigurerAdapter類的configure方法實現(xiàn):
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();
}
我們在定義自己的過濾器鏈的時候,可以繼承WebSecurityConfigurerAdapter重寫configure方法抠璃,比如:
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers() // 指定當(dāng)前`SecurityFilterChain`實例匹配哪些請求
.anyRequest().and()
.authorizeRequests() // 攔截請求长搀,創(chuàng)建FilterSecurityInterceptor
.anyRequest().authenticated() // 在創(chuàng)建過濾器的基礎(chǔ)上的一些自定義配置
.and() // 用and來表示配置過濾器結(jié)束,以便進(jìn)行下一個過濾器的創(chuàng)建和配置
.formLogin().and() // 設(shè)置表單登錄鸡典,創(chuàng)建UsernamePasswordAuthenticationFilter
.httpBasic(); // basic驗證源请,創(chuàng)建BasicAuthenticationFilter
}
2.3. performBuild(WebSecurity)
遍歷securityFilterChainBuilders(其實就是HttpSecurity)列表調(diào)用其build方法,生成SecurityFilterChain實例,最后利用多個SecurityFilterChain實例組成List谁尸,再封裝到FilterChainProxy舅踪。
protected Filter performBuild() throws Exception {
Assert.state(!this.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 = 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]));
}
var3 = this.securityFilterChainBuilders.iterator();
while(var3.hasNext()) {
SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder = (SecurityBuilder)var3.next();
securityFilterChains.add(securityFilterChainBuilder.build());
}
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();
return (Filter)result;
}
securityFilterChainBuilders(其實就是HttpSecurity)的build方法,內(nèi)部最后也是調(diào)用自己的init良蛮、configure抽碌、performBuild。
2.3.1. init(HttpSecurity)
會遍歷所有之前加載好的配置類configuator决瞳,調(diào)用其init
配置類的init一般是配置HttpSecurity货徙。以HttpBasicConfigurer配置類為例:
public void init(B http) throws Exception {
this.registerDefaults(http);
}
private void registerDefaults(B http) {
ContentNegotiationStrategy contentNegotiationStrategy = (ContentNegotiationStrategy)http.getSharedObject(ContentNegotiationStrategy.class);
if (contentNegotiationStrategy == null) {
contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
}
MediaTypeRequestMatcher restMatcher = new MediaTypeRequestMatcher((ContentNegotiationStrategy)contentNegotiationStrategy, new MediaType[]{MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML, MediaType.MULTIPART_FORM_DATA, MediaType.TEXT_XML});
restMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
RequestMatcher notHtmlMatcher = new NegatedRequestMatcher(new MediaTypeRequestMatcher((ContentNegotiationStrategy)contentNegotiationStrategy, new MediaType[]{MediaType.TEXT_HTML}));
RequestMatcher restNotHtmlMatcher = new AndRequestMatcher(Arrays.asList(notHtmlMatcher, restMatcher));
RequestMatcher preferredMatcher = new OrRequestMatcher(Arrays.asList(X_REQUESTED_WITH, restNotHtmlMatcher));
this.registerDefaultEntryPoint(http, preferredMatcher);
this.registerDefaultLogoutSuccessHandler(http, preferredMatcher);
}
2.3.2. configure(HttpSecurity)
會遍歷所有之前加載好的配置類configuator,調(diào)用其configure
配置類的configure一般構(gòu)造Filter皮胡,添加到HttpSecurity的Filter列表中痴颊,作為過濾器鏈的其中一個。以HttpBasicConfigurer配置類為例:
public void configure(B http) throws Exception {
AuthenticationManager authenticationManager = (AuthenticationManager)http.getSharedObject(AuthenticationManager.class);
BasicAuthenticationFilter basicAuthenticationFilter = new BasicAuthenticationFilter(authenticationManager, this.authenticationEntryPoint);
if (this.authenticationDetailsSource != null) {
basicAuthenticationFilter.setAuthenticationDetailsSource(this.authenticationDetailsSource);
}
RememberMeServices rememberMeServices = (RememberMeServices)http.getSharedObject(RememberMeServices.class);
if (rememberMeServices != null) {
basicAuthenticationFilter.setRememberMeServices(rememberMeServices);
}
basicAuthenticationFilter = (BasicAuthenticationFilter)this.postProcess(basicAuthenticationFilter);
http.addFilter(basicAuthenticationFilter);
}
2.3.3. performBuild(HttpSecurity)
將List<Filter>以及matcher封裝成SecurityFilterChain
protected DefaultSecurityFilterChain performBuild() throws Exception {
Collections.sort(this.filters, this.comparator);
return new DefaultSecurityFilterChain(this.requestMatcher, this.filters);
}
請求來時屡贺,F(xiàn)ilterChainProxy如何起作用
請求到達(dá)的時候蠢棱,F(xiàn)ilterChainProxy的dofilter()方法內(nèi)部,會遍歷所有的SecurityFilterChain甩栈,匹配url泻仙,第一個匹配到之后則調(diào)用該SecurityFilterChain中的List<filter>依次做認(rèn)證或鑒權(quán),執(zhí)行最后調(diào)用外層傳入的filterchain量没,將控制權(quán)交給上層過濾鏈玉转,即spring 的過濾鏈。
核心邏輯如下:
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);
HttpServletResponse fwResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);
List<Filter> filters = this.getFilters((HttpServletRequest)fwRequest);//選擇匹配的過濾器鏈
if (filters != null && filters.size() != 0) {
FilterChainProxy.VirtualFilterChain vfc = new FilterChainProxy.VirtualFilterChain(fwRequest, chain, filters);
vfc.doFilter(fwRequest, fwResponse);//將請求交給該過濾器鏈殴蹄,逐個通過filter進(jìn)行驗證
} else {
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);//security邏輯已執(zhí)行完冤吨,交給上層
}
}
//取第一個匹配的過濾器鏈
private List<Filter> getFilters(HttpServletRequest request) {
Iterator var2 = this.filterChains.iterator();
SecurityFilterChain chain;
do {
if (!var2.hasNext()) {
return null;
}
chain = (SecurityFilterChain)var2.next();
} while(!chain.matches(request));
return chain.getFilters();
}