參考鏈接(主要參考此係列文章,截圖和補充總結等由debug程序、閱讀源碼得出)
https://juejin.im/post/5d8d66aee51d45783f5aa49e
一、三句話解釋框架原理
1、整個框架的核心是一個過濾器,這個過濾器名字叫springSecurityFilterChain類型是FilterChainProxy
2、核心過濾器裏面是過濾器鏈(列表),過濾器鏈的每個元素都是一組URL對應一組過濾器
3、WebSecurity用來創建FilterChainProxy過濾器,
HttpSecurity用來創建過濾器鏈的每個元素。
補充解釋:WebSecurity和HttpSecurity都是建造者
WebSecurity構建目標是FilterChainProxy對象
HttpSecurity的構建目標僅僅是FilterChainProxy中的一個SecurityFilterChain
@EnableWebSecurity註解,導入了WebSecurityConfiguration類
WebSecurityConfiguration中創建了建造者對象WebSecurity,和核心過濾器FilterChainProxy
二、框架接口設計
1、關注兩個東西:建造者和適配器,框架的用法就是通過適配器對建造者進行配置
2、框架用法是寫一個自定義配置類,繼承WebSecurityConfigurerAdapter,重寫幾個configure()方法
WebSecurityConfigurerAdapter就是Web安全配置器的適配器對象。
// 安全建造者 // 顧名思義是一個builder構造器,創建並返回一個類型爲O的對象
// 安全建造者
// 顧名思義是一個builder構造器,創建並返回一個類型爲O的對象
public interface SecurityBuilder<O> {
O build() throws Exception;
}
// 抽象安全建造者
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
private AtomicBoolean building = new AtomicBoolean();
private O object;
public final O build() throws Exception {
// 限定build()只會進行一次!
if (this.building.compareAndSet(false, true)) {
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
}
// 子類需要重寫doBuild()方法
protected abstract O doBuild() throws Exception;
}
// 配置後的抽象安全建造者
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
extends AbstractSecurityBuilder<O> {
// 實現了doBuild()方法,遍歷configurers進行init()和configure()。
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;
}
}
// 它的子類HttpSecurity和WebSecurity都實現了它的performBuild()方法!!!
protected abstract O performBuild() throws Exception;
// 主要作用是將安全配置器SecurityConfigurer注入到屬性configurers中,
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);
}
}
}
// 安全配置器,配置建造者B,B可以建造O
// 初始化(init)SecurityBuilder,且配置(configure)SecurityBuilder
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
void init(B builder) throws Exception;
void configure(B builder) throws Exception;
}
// Web安全配置器,配置建造者T,T可以建造web過濾器
public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>>
extends SecurityConfigurer<Filter, T> {
}
// Web安全配置器的適配器
// 配置建造者WebSecurity,WebSecurity可以建造核心過濾器
public abstract class WebSecurityConfigurerAdapter
implements WebSecurityConfigurer<WebSecurity> {
}
// 用於構建FilterChainProxy的建造者
public final class WebSecurity
extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity>
implements
SecurityBuilder<Filter>, ApplicationContextAware {
}
// 用於構建SecurityFilterChain的建造者
public final class HttpSecurity
extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
implements
SecurityBuilder<DefaultSecurityFilterChain>,
HttpSecurityBuilder<HttpSecurity> {
}
3、總結:
看到建造者去看他的方法:build(); doBuid(); init(); configure(); performBuilde();
看到配置器去看他的方法: init(); config();
三、從寫MySecurityConfig時使用的@EnableWebSecurity註解開始看源碼:
1、@EnableWebSecurity註解導入了三個類,重點關注WebSecurityConfiguration
2、WebSecurityConfiguration中需要關注兩個方法:
setFilterChainProxySecurityConfigurer()方法:創建了WebSecurity建造者對象,用於後面建造FilterChainProxy過濾器。
springSecurityFilterChain()方法:調用WebSecurity.build(),建造出FilterChainProxy過濾器對象。
四、WebSecurity的創建過程(由WebSecurityConfiguration創建)
1、setFilterChainProxySecurityConfigurer()方法負責收集配置累對象列表webSecurityConfigurers,並創建WebSecurity:
/**
* Sets the {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>}
* instances used to create the web configuration.
*
* @param objectPostProcessor the {@link ObjectPostProcessor} used to create a
* {@link WebSecurity} instance
* @param webSecurityConfigurers the
* {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to
* create the web configuration
* @throws Exception
*/
@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);
}
webSecurityConfigurers.sort(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、下圖是通過AutowiredWebSecurityConfigurersIgnoreParents的getWebSecurityConfigurers()方法,獲取所有實現WebSecurityConfigurer的配置類。
五、FilterChainProxy的創建過程(由WebSecurityConfiguration創建)
1、在springSecurityFilterChain()方法中調用webSecurity.build()創建了FilterChainProxy。
PS:根據下面代碼,我們可以知道如果創建的MySecurityConfig類沒有被sping掃描到,
框架會新new 出一個WebSecurityConfigureAdapter對象,這會導致我們配置的用戶名和密碼失效。
2、WebSecurity是一個建造者,所以我們去看這些方法build(); doBuild(); init(); configure(); performBuild();
build()方法定義在WebSecurity對象的父類AbstractSecurityBuilder中:
build()方法會調用WebSecurity對象的父類AbstractConfiguredSecurityBuilder#doBuild():
doBuild()先調用init();configure();等方法
我們上面已經得知了configurersAddedInInitializing裏是所有的配置類對象
如下圖,這裏會依次執行配置類的configure();init()方法
doBuild()最後調用了WebSecurity對象的perfomBuild(),來創建了FilterChainProxy對象
performBuild()裏遍歷securityFilterChainBuilders建造者列表
把每個SecurityBuilder建造者對象構建成SecurityFilterChain實例
最後創建並返回FilterChainProxy
3、securityFilterChainBuilders建造者列表的初始化
這時候要注意到WebSecurityConfigurerAdapter,這個類的創建了HttpSecurity並放入了securityFilterChainBuilders
WebSecurityConfigurerAdapter是一個安全配置器,我們知道建造者在performBuild()之前都會把循環調用安全配置器的init();configure();方法,然後創建HttpSecurity並放入自己的securityFilterChainBuilders裏。
PS: 前面已經提到了,在WebSecurity初始化時,會依次將WebSecurityConfigurerAdapter的子類放入WebSecurity。
六、ServletContext如何拿到FilterChainProxy的過濾器對象
1、Bean都是存在Spring的Bean工廠裏的,而且在Web項目中Servlet、Filter、Listener都要放入ServletContext中。
2、看下面這張圖,ServletContainerInitializer接口提供了一個onStartup()方法,用於在Servlet容器啓動時動態註冊一些對象到ServletContext中。
官方的解釋是:爲了支持可以不使用web.xml。提供了ServletContainerInitializer,它可以通過SPI機制,當啓動web容器的時候,會自動到添加的相應jar包下找到META-INF/services下以ServletContainerInitializer的全路徑名稱命名的文件,它的內容爲ServletContainerInitializer實現類的全路徑,將它們實例化。
Spring框架通過META-INF配置了SpringServletContainerInitializer類
SpringServletContainerInitializer實現了ServletContainerInitializer接口。
請注意該類上的@HandlesTypes(WebApplicationInitializer.class)註解
根據Sevlet3.0規範,Servlet容器在調用onStartup()方法時,會以Set集合的方式注入WebApplicationInitializer的子類(包括接口,抽象類)。然後會依次調用WebApplicationInitializer的實現類的onStartup方法,從而起到啓動web.xml相同的作用(添加servlet,listener實例到ServletContext中)。
Spring Security中的AbstractSecurityWebApplicationInitializer就是WebApplicationInitializer的抽象子類.
當執行到下面的onStartup()方法時,會調用insertSpringSecurityFilterChain()
將類型爲FilterChainProxy名稱爲springSecurityFilterChain的過濾器對象用DelegatingFilterProxy包裝,然後注入ServletContext
七、FilterChainProxy運行過程
請求到達的時候,FilterChainProxy的dofilter()方法內部,會遍歷所有的SecurityFilterChain,對匹配到的url,則一一調用SecurityFilterChain中的filter做認證或授權。
public class FilterChainProxy extends GenericFilterBean {
private final static String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
private List<SecurityFilterChain> filterChains;
private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
private HttpFirewall firewall = new StrictHttpFirewall();
public FilterChainProxy() {
}
public FilterChainProxy(SecurityFilterChain chain) {
this(Arrays.asList(chain));
}
public FilterChainProxy(List<SecurityFilterChain> filterChains) {
this.filterChains = filterChains;
}
@Override
public void afterPropertiesSet() {
filterChainValidator.validate(this);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (clearContext) {
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
doFilterInternal(request, response, chain);
}
finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
else {
doFilterInternal(request, response, chain);
}
}
private void doFilterInternal(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = firewall.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse fwResponse = firewall.getFirewalledResponse((HttpServletResponse) response);
// 根據當前請求,獲得一組過濾器鏈
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);
// 請求依次經過這組濾器鏈
vfc.doFilter(fwRequest, fwResponse);
}
/**
* 根據Request請求獲得一個過濾器鏈
*/
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
/**
* 根據URL獲得一個過濾器鏈
*/
public List<Filter> getFilters(String url) {
return getFilters(firewall.getFirewalledRequest((new FilterInvocation(url, null)
.getRequest())));
}
/**
* 返回一個過濾器鏈
*/
public List<SecurityFilterChain> getFilterChains() {
return Collections.unmodifiableList(filterChains);
}
// 過濾器鏈內部類
private static class VirtualFilterChain implements FilterChain {
private final FilterChain originalChain;
private final List<Filter> additionalFilters;
private final FirewalledRequest firewalledRequest;
private final int size;
private int currentPosition = 0;
private VirtualFilterChain(FirewalledRequest firewalledRequest,
FilterChain chain, List<Filter> additionalFilters) {
this.originalChain = chain;
this.additionalFilters = additionalFilters;
this.size = additionalFilters.size();
this.firewalledRequest = firewalledRequest;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (currentPosition == size) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " reached end of additional filter chain; proceeding with original chain");
}
// Deactivate path stripping as we exit the security filter chain
this.firewalledRequest.reset();
originalChain.doFilter(request, response);
}
else {
currentPosition++;
Filter nextFilter = additionalFilters.get(currentPosition - 1);
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " at position " + currentPosition + " of " + size
+ " in additional filter chain; firing Filter: '"
+ nextFilter.getClass().getSimpleName() + "'");
}
nextFilter.doFilter(request, response, this);
}
}
}
public interface FilterChainValidator {
void validate(FilterChainProxy filterChainProxy);
}
private static class NullFilterChainValidator implements FilterChainValidator {
@Override
public void validate(FilterChainProxy filterChainProxy) {
}
}
}
補充:WebSecurity在初始化的時候會掃描WebSecurityConfigurerAdapter配置器適配器的子類(即生成HttpSecurity配置器)。
所有的配置器會被調用init();configure();初始化配置,其中生成的每個HttpSecurity配置器都代表了一個過濾器鏈。
PS:如果有多個WebSecurityConfigurerAdapter配置器適配器的子類,會產生多個SecurityFilterChain過濾器鏈實例。Spring Security Oauth2的拓展就是這麼做的。
八、spring security 怎麼創建的過濾器
1、我們已經知道了springSecurityFilterChain(類型爲FilterChainProxy)是實際起作用的過濾器鏈,DelegatingFilterProxy起到代理作用。
2、我們創建的MySecurityConfig繼承了WebSecurityConfigurerAdapter。WebSecurityConfigurerAdapter就是用來創建過濾器鏈,重寫的configure(HttpSecurity http)的方法就是用來配置HttpSecurity的。
protected void configure(HttpSecurity http) throws Exception { http .requestMatchers() // 指定當前`SecurityFilterChain`實例匹配哪些請求 .anyRequest().and() .authorizeRequests() // 攔截請求,創建FilterSecurityInterceptor .anyRequest().authenticated() // 在創建過濾器的基礎上的一些自定義配置 .and() // 用and來表示配置過濾器結束,以便進行下一個過濾器的創建和配置 .formLogin().and() // 設置表單登錄,創建UsernamePasswordAuthenticationFilter .httpBasic(); // basic驗證,創建BasicAuthenticationFilter } 鏈接:https://juejin.im/post/5d9161ace51d4577ff0d9eb7 來源:掘金 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
3、http.authorizeRequests()、http.formLogin()、http.httpBasic()分別創建了ExpressionUrlAuthorizationConfigurer,FormLoginConfigurer,HttpBasicConfigurer。在三個類從父級一直往上找,會發現它們都是SecurityConfigurer建造器的子類。SecurityConfigurer中又有configure()方法。該方法被子類實現就用於創建各個過濾器,並將過濾器添加進HttpSecurity中維護的裝有Filter的List中,比如HttpBasicConfigurer中的configure方法,源碼如下:
HttpSecurity作爲建造者會把根據api把這些配置器添加到實例中
這些配置器中大都是創建了相應的過濾器,並進行配置,最終在HttpSecurity建造SecurityFilterChain實例時放入過濾器鏈
九、認證過濾器 UsernamePasswordAuthenticationFilter
1、參數有username,password的,走UsernamePasswordAuthenticationFilter,提取參數構造UsernamePasswordAuthenticationToken進行認證,成功則填充SecurityContextHolder的Authentication
2、UsernamePasswordAuthenticationFilter實現了其父類AbstractAuthenticationProcessingFilter中的attemptAuthentication方法。這個方法會調用認證管理器AuthenticationManager去認證。
3、關閉formLogin()驗證後則不通過UsernamePasswordAuthenticationFilter驗證,可自定義攔截器進行驗證攔截
十、默認驗證流程
1、開啓formLogin()驗證後,請求會被UsernamePasswordAuthenticationFilter攔截,程序事先在InMemoryUserDetailsManager中保存了一個user在Map<String, MutableUserDetails> users 裏面。攔截器會將兩者進行比對。
2、默認用戶信息是在類SecurityProperties中生成,程序啓動時UserDetailsServiceAutoConfiguration調用inMemoryUserDetailsManager方法,內部調用getOrDeducePassword方法打印出默認的生成的密碼。
十一、Spring組件@Scope
1、Bean的作用域
Singleton(單例式):在整個應用中,只創建bean的一個實例。
Prototype(原型式):每次注入或者通過Spring應用上下文獲取的時候,都會創建一個新的bean實例。
Session(會話式):在Web應用中,爲每個會話創建一個bean實例。(eg:電子商務應用中,一個bean代表一個用戶的購物車,只要同一個session一個bean)。
Request(請求式):在Web應用中,爲每個請求創建一個bean實例。
鏈接:https://www.cnblogs.com/MrSi/p/7932218.html
2、每個客戶端登錄都會產生一個session會話,它的生命週期 從登錄系統到 session過期,期間session上存儲的信息都是有效可用的,我習慣於叫它會話級的緩存,像用戶登錄的身份信息我們一般都會綁定到這個session上。這裏我們要講的@Scope("session"),就是spring提供出來的一個會話級bean方案,在這種模式下,用spring的DI功能來獲取組件,可以做到在會話的生命週期中這個組件只有一個實例。
3、接下來再說請求(request),http協議的處理模型,從客戶端發起request請求,到服務端的處理,最後response給客戶端,我們稱爲一次完整的請求。在這樣的一次請求過程中,我們的服務站可能要串行調用funcA->funcB->funcC·... 這樣的一串函數,假如我們的系統需要做細緻的權限校驗(用戶權限,數據權限),更可怕的是funcA,funcB,funcC是3個人分別實現的,而且都要做權限校驗。那麼極有可能會出現3個人各連接了一次數據庫,讀取了同一批權限數據。這裏想象一下,假如一個數據讀取要花2秒,那麼3個方法就要花費6秒的處理時間。但實際上這些數據只用在這個請求過程中讀取一次,緩存在request上下文環境中,我習慣稱之爲線程級緩存。關於線程級緩存java有ThreadLocal方案,像Hibernate的事務就是用這種方案維持一次執行過程中數據庫連接的唯一。當然,今天要講的@Scope("request")也可以做到這種線程級別的緩存。