SpringSecurity攔截器鏈的使用詳解
SpringSecurity攔截器鏈
Spring版本
<!--Spring Security過濾器鏈,注意過濾器名稱必須叫springSecurityFilterChain--> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
SpringBoot 版本
攔截器鏈創(chuàng)建的過程
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class,
OAuth2ImportSelector.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;
}當(dāng)我們使用這個注解的時候,等同于把WebSecurityConfiguration類放到了Spring的IOC容器中,在這個時候進行了初始化。
@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);
}
//調(diào)用webSecurity的build方法,生成過濾器鏈。
return webSecurity.build();
}webSecurity的build方法最終調(diào)用的是doBuild方法。
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");
}doBuild方法調(diào)用的市webSecurity的performBuild方法。
@Override
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING;
beforeInit();
init();
buildState = BuildState.CONFIGURING;
beforeConfigure();
configure();
buildState = BuildState.BUILDING;
//在BUILDING階段調(diào)用webSecurity的performBuild方法
O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}webSecurity完成所有過濾器的插件,最終返回的是過濾器鏈代理類filterChainProxy
@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);
//會創(chuàng)建要忽略的和要認證的攔截器鏈
for (RequestMatcher ignoredRequest : ignoredRequests) {
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
}
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
//過濾器鏈實際是被filterChainProxy代理的
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;
}斷點圖如下:

FilterChainProxy間接繼承了Filter,可以作為真正的過濾器使用。它會攜帶若干條過濾器鏈,并在承擔(dān)過濾器職責(zé),將其派發(fā)到過濾器鏈上的每一個過濾器上。
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);
//派發(fā)到過濾器鏈上
doFilterInternal(request, response, chain);
}
finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
else {
doFilterInternal(request, response, chain);
}
}
//是真正執(zhí)行虛擬過濾器鏈邏輯的方法。
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);
}
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);
}
}請求的過程
ApplicationFilterChain是tomcat中的攔截器鏈,ApplicationFilterChain對象的特點與創(chuàng)建特點:經(jīng)過一路代碼跟蹤發(fā)現(xiàn),每一個url匹配模式對應(yīng)于一個ApplicationFilterChain對象,在應(yīng)用的整個生命周期中只在第一次被訪問時被創(chuàng)建一次,有種單例模式的感覺,但是并沒有做線程安全方面的處理,后來發(fā)現(xiàn)可能做了池化處理;創(chuàng)建:ApplicationFilterChain對象是在StandardWrapperValve類中的invoke方法中調(diào)ApplicationFilterFactory.createFilterChain方法創(chuàng)建的,在實例化完成后緊接著就是為其設(shè)置Servlet,添加Filters以及設(shè)置其他屬性,添加Filters依賴于一個FilterMap[]數(shù)組,該數(shù)組的賦值與擴容過程在StandardContext類中實現(xiàn),至于FilterMap的生成,則是在Spring的Bean初始化階段,通過掃描包下所有的類并結(jié)合注解通過反射機制進行實例化的。
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
try {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedExceptionAction<Void>() {
@Override
public Void run()
throws ServletException, IOException {
internalDoFilter(req,res);
return null;
}
}
);
} catch( PrivilegedActionException pe) {
Exception e = pe.getException();
if (e instanceof ServletException)
throw (ServletException) e;
else if (e instanceof IOException)
throw (IOException) e;
else if (e instanceof RuntimeException)
throw (RuntimeException) e;
else
throw new ServletException(e.getMessage(), e);
}
} else {
internalDoFilter(request,response);
}
}
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
try {
Filter filter = filterConfig.getFilter();
if (request.isAsyncSupported() && "false".equalsIgnoreCase(
filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
}
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
} else {
filter.doFilter(request, response, this);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
sthrow new ServletException(sm.getString("filterChain.filter"), e);
}
return;
}
其中會優(yōu)先調(diào)用springsecurity的攔截器鏈,它的本質(zhì)還是有一個fiter,注意,這里第5個過濾器顯示在攔截器鏈后執(zhí)行的,但是在攔截器鏈里面執(zhí)行過了,就不會這執(zhí)行了。

當(dāng)請求到攔截鏈的時候

依次執(zhí)行過濾器鏈

到此這篇關(guān)于SpringSecurity攔截器鏈的使用詳解的文章就介紹到這了,更多相關(guān)SpringSecurity攔截器鏈內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringSecurity自定義資源攔截規(guī)則及登錄界面跳轉(zhuǎn)問題
- springsecurity實現(xiàn)攔截器的使用示例
- SpringBoot整合SpringSecurity實現(xiàn)認證攔截的教程
- Swagger2不被SpringSecurity框架攔截的配置及說明
- Spring Boot security 默認攔截靜態(tài)資源的解決方法
- SpringSecurity實現(xiàn)動態(tài)url攔截(基于rbac模型)
- Spring Security攔截器引起Java CORS跨域失敗的問題及解決
- SpringBoot+SpringSecurity 不攔截靜態(tài)資源的實現(xiàn)
- 淺談Spring Security 對于靜態(tài)資源的攔截與放行
- spring Security配置攔截規(guī)則小結(jié)
相關(guān)文章
springboot集成redis并使用redis生成全局唯一索引ID
本文主要介紹了springboot集成redis并使用redis生成全局唯一索引ID,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03
@RequestMapping 如何使用@PathVariable 從URI中獲取參數(shù)
這篇文章主要介紹了@RequestMapping 如何使用@PathVariable 從URI中獲取參數(shù)的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
SpringBoot中實現(xiàn)多數(shù)據(jù)源連接和切換的方案
在Spring Boot中,通過AbstractRoutingDataSource實現(xiàn)多數(shù)據(jù)源連接是一種常見的做法,這種技術(shù)允許你在運行時動態(tài)地切換數(shù)據(jù)源,從而支持對多個數(shù)據(jù)庫的操作,本文給大家介紹了SpringBoot中實現(xiàn)多數(shù)據(jù)源連接和切換的方案,需要的朋友可以參考下2024-11-11
SpringBoot中利用AOP和攔截器實現(xiàn)自定義注解
本文將通過攔截器+AOP實現(xiàn)自定義注解,在這里攔截器充當(dāng)在指定注解處要執(zhí)行的方法,aop負責(zé)將攔截器的方法和要注解生效的地方做一個織入,感興趣的可以嘗試一下2022-06-06
springboot斷言異常封裝與統(tǒng)一異常處理實現(xiàn)代碼
異常處理其實一直都是項目開發(fā)中的大頭,但關(guān)注異常處理的人一直都特別少,下面這篇文章主要給大家介紹了關(guān)于springboot斷言異常封裝與統(tǒng)一異常處理的相關(guān)資料,需要的朋友可以參考下2023-01-01
Spring通過ApplicationContext主動獲取bean的方法講解
今天小編就為大家分享一篇關(guān)于Spring通過ApplicationContext主動獲取bean的方法講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03

