SpringSecurity攔截器鏈的使用詳解
SpringSecurity攔截器鏈
Spring版本
<!--Spring Security過(guò)濾器鏈,注意過(guò)濾器名稱必須叫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)建的過(guò)程
@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)我們使用這個(gè)注解的時(shí)候,等同于把WebSecurityConfiguration類放到了Spring的IOC容器中,在這個(gè)時(shí)候進(jìn)行了初始化。
@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方法,生成過(guò)濾器鏈。
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完成所有過(guò)濾器的插件,最終返回的是過(guò)濾器鏈代理類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);
//會(huì)創(chuàng)建要忽略的和要認(rèn)證的攔截器鏈
for (RequestMatcher ignoredRequest : ignoredRequests) {
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
}
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
//過(guò)濾器鏈實(shí)際是被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;
}斷點(diǎn)圖如下:

FilterChainProxy間接繼承了Filter,可以作為真正的過(guò)濾器使用。它會(huì)攜帶若干條過(guò)濾器鏈,并在承擔(dān)過(guò)濾器職責(zé),將其派發(fā)到過(guò)濾器鏈上的每一個(gè)過(guò)濾器上。
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ā)到過(guò)濾器鏈上
doFilterInternal(request, response, chain);
}
finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
else {
doFilterInternal(request, response, chain);
}
}
//是真正執(zhí)行虛擬過(guò)濾器鏈邏輯的方法。
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);
}
}請(qǐng)求的過(guò)程
ApplicationFilterChain是tomcat中的攔截器鏈,ApplicationFilterChain對(duì)象的特點(diǎn)與創(chuàng)建特點(diǎn):經(jīng)過(guò)一路代碼跟蹤發(fā)現(xiàn),每一個(gè)url匹配模式對(duì)應(yīng)于一個(gè)ApplicationFilterChain對(duì)象,在應(yīng)用的整個(gè)生命周期中只在第一次被訪問(wèn)時(shí)被創(chuàng)建一次,有種單例模式的感覺(jué),但是并沒(méi)有做線程安全方面的處理,后來(lái)發(fā)現(xiàn)可能做了池化處理;創(chuàng)建:ApplicationFilterChain對(duì)象是在StandardWrapperValve類中的invoke方法中調(diào)ApplicationFilterFactory.createFilterChain方法創(chuàng)建的,在實(shí)例化完成后緊接著就是為其設(shè)置Servlet,添加Filters以及設(shè)置其他屬性,添加Filters依賴于一個(gè)FilterMap[]數(shù)組,該數(shù)組的賦值與擴(kuò)容過(guò)程在StandardContext類中實(shí)現(xiàn),至于FilterMap的生成,則是在Spring的Bean初始化階段,通過(guò)掃描包下所有的類并結(jié)合注解通過(guò)反射機(jī)制進(jìn)行實(shí)例化的。
@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;
}
其中會(huì)優(yōu)先調(diào)用springsecurity的攔截器鏈,它的本質(zhì)還是有一個(gè)fiter,注意,這里第5個(gè)過(guò)濾器顯示在攔截器鏈后執(zhí)行的,但是在攔截器鏈里面執(zhí)行過(guò)了,就不會(huì)這執(zhí)行了。

當(dāng)請(qǐng)求到攔截鏈的時(shí)候

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

到此這篇關(guān)于SpringSecurity攔截器鏈的使用詳解的文章就介紹到這了,更多相關(guān)SpringSecurity攔截器鏈內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringSecurity自定義資源攔截規(guī)則及登錄界面跳轉(zhuǎn)問(wèn)題
- springsecurity實(shí)現(xiàn)攔截器的使用示例
- SpringBoot整合SpringSecurity實(shí)現(xiàn)認(rèn)證攔截的教程
- Swagger2不被SpringSecurity框架攔截的配置及說(shuō)明
- Spring Boot security 默認(rèn)攔截靜態(tài)資源的解決方法
- SpringSecurity實(shí)現(xiàn)動(dòng)態(tài)url攔截(基于rbac模型)
- Spring Security攔截器引起Java CORS跨域失敗的問(wèn)題及解決
- SpringBoot+SpringSecurity 不攔截靜態(tài)資源的實(shí)現(xiàn)
- 淺談Spring Security 對(duì)于靜態(tài)資源的攔截與放行
- spring Security配置攔截規(guī)則小結(jié)
相關(guān)文章
sonar-scanner連接sonarquebe7的sonar.java.binaries問(wèn)題的解決方案
今天小編就為大家分享一篇關(guān)于sonar-scanner連接sonarquebe7的sonar.java.binaries問(wèn)題的解決方案,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12
6種常見(jiàn)的SpringBoot攔截器使用場(chǎng)景及實(shí)現(xiàn)方式
這篇文章主要為大家詳細(xì)介紹了SpringBoot中6種常見(jiàn)的攔截器使用場(chǎng)景及其實(shí)現(xiàn)方式,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-04-04
Java實(shí)現(xiàn)自動(dòng)壓縮文件并加密的方法示例
這篇文章主要介紹了Java實(shí)現(xiàn)自動(dòng)壓縮文件并加密的方法,涉及java針對(duì)文件進(jìn)行zip壓縮并加密的相關(guān)操作技巧,需要的朋友可以參考下2018-01-01
解決mybatis-plus使用jdk8的LocalDateTime 查詢時(shí)報(bào)錯(cuò)的方法
這篇文章主要介紹了解決mybatis-plus使用jdk8的LocalDateTime 查詢時(shí)報(bào)錯(cuò)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
Java案例之隨機(jī)驗(yàn)證碼功能實(shí)現(xiàn)實(shí)例
本篇文章主要介紹了Java案例之隨機(jī)驗(yàn)證碼功能實(shí)現(xiàn)實(shí)例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-06-06
Java NIO Path接口和Files類配合操作文件的實(shí)例
下面小編就為大家分享一篇Java NIO Path接口和Files類配合操作文件的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-11-11
SpringBoot項(xiàng)目啟動(dòng)時(shí)執(zhí)行指定的方法
本文介紹了在SpringBoot項(xiàng)目中通過(guò)@PostConstruct注解和實(shí)現(xiàn)CommandLineRunner接口來(lái)在啟動(dòng)時(shí)執(zhí)行方法,具有一定的參考價(jià)值,感興趣的可以了解一下2025-08-08
MyBatis?Plus如何實(shí)現(xiàn)獲取自動(dòng)生成主鍵值
這篇文章主要介紹了MyBatis?Plus如何實(shí)現(xiàn)獲取自動(dòng)生成主鍵值問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09

