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)文章
springboot集成redis并使用redis生成全局唯一索引ID
本文主要介紹了springboot集成redis并使用redis生成全局唯一索引ID,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03@RequestMapping 如何使用@PathVariable 從URI中獲取參數(shù)
這篇文章主要介紹了@RequestMapping 如何使用@PathVariable 從URI中獲取參數(shù)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08SpringBoot中實(shí)現(xiàn)多數(shù)據(jù)源連接和切換的方案
在Spring Boot中,通過(guò)AbstractRoutingDataSource實(shí)現(xiàn)多數(shù)據(jù)源連接是一種常見(jiàn)的做法,這種技術(shù)允許你在運(yùn)行時(shí)動(dòng)態(tài)地切換數(shù)據(jù)源,從而支持對(duì)多個(gè)數(shù)據(jù)庫(kù)的操作,本文給大家介紹了SpringBoot中實(shí)現(xiàn)多數(shù)據(jù)源連接和切換的方案,需要的朋友可以參考下2024-11-11SpringBoot中利用AOP和攔截器實(shí)現(xiàn)自定義注解
本文將通過(guò)攔截器+AOP實(shí)現(xiàn)自定義注解,在這里攔截器充當(dāng)在指定注解處要執(zhí)行的方法,aop負(fù)責(zé)將攔截器的方法和要注解生效的地方做一個(gè)織入,感興趣的可以嘗試一下2022-06-06springboot斷言異常封裝與統(tǒng)一異常處理實(shí)現(xiàn)代碼
異常處理其實(shí)一直都是項(xiàng)目開(kāi)發(fā)中的大頭,但關(guān)注異常處理的人一直都特別少,下面這篇文章主要給大家介紹了關(guān)于springboot斷言異常封裝與統(tǒng)一異常處理的相關(guān)資料,需要的朋友可以參考下2023-01-01Java比較兩個(gè)對(duì)象中全部屬性值是否相等的方法
本文主要介紹了Java比較兩個(gè)對(duì)象中全部屬性值是否相等的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08Spring通過(guò)ApplicationContext主動(dòng)獲取bean的方法講解
今天小編就為大家分享一篇關(guān)于Spring通過(guò)ApplicationContext主動(dòng)獲取bean的方法講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03