運行時動態的開關 Spring Security

1. 爲何要在運行時動態的開關 Spring Security?

        考慮這樣一個場景,當咱們構建了一整套微服務架構的系統後,公司某個內部的老系統也感覺到了微服務架構的好處,包括實時監控,限流,熔斷,高可用的機制等等,老系統的開發人員也但願能減小本身的一些工做量,因此他們系統將老系統加入到咱們的微服務架構體系中來。這樣就產生了一些適配,兼容性問題,若是讓老系統來徹底適配已經構建好的微服務架構體系那麼老系統改動的代價就比較大,包括技術的升級,開發人員的學習成本提升,測試問題,還有老系統還有一些不斷的新需求要開發。比較理想的解決方案是對老系統的改動越小越好,最好能作到無縫集成,已經構建好的微服務架構來爲老系統的集成提供支持。好比說老系統本來有本身的認證,受權控制,使用了 Spring Security ,在微服務架構中咱們將認證,受權的工做統一放在了 API 網關層去處理。這樣就和老系統的集成產生了衝突。因而我就須要讓 API 網關路由到老系統上的請求不通過老系統自身的認證、受權流程,也能夠正常訪問。同時也不能破壞當不經過 API 網關訪問時老系統的認證、受權流程也要能正常工做。因此這是我要達到的目的。java

2. Spring Security 在 Web 項目中是如何工做的

        這是我在網上找的一張圖,目的就是爲了大概說明問題。 Spring Web Security 的核心功能都是在這一條過濾器鏈上完成的。具體能夠參考這個類 :web

org.springframework.security.config.annotation.web.builders.FilterComparator , 這個類中定義了全部的 Spring Security 的過濾器以及他們的順序。spring

搞清楚這一點,我就有一個想法,既然我想要關閉 Spring Security 不讓他起做用 ,那我不讓請求通過這些過濾器不就能夠了麼。架構

FilterComparator 源碼 :app

private static final int STEP = 100;
	private Map<String, Integer> filterToOrder = new HashMap<String, Integer>();

FilterComparator() {
		int order = 100;
		put(ChannelProcessingFilter.class, order);
		order += STEP;
		put(ConcurrentSessionFilter.class, order);
		order += STEP;
		put(WebAsyncManagerIntegrationFilter.class, order);
		order += STEP;
		put(SecurityContextPersistenceFilter.class, order);
		order += STEP;
		put(HeaderWriterFilter.class, order);
		order += STEP;
		put(CorsFilter.class, order);
		order += STEP;
		put(CsrfFilter.class, order);
		order += STEP;
		put(LogoutFilter.class, order);
		order += STEP;
		put(X509AuthenticationFilter.class, order);
		order += STEP;
		put(AbstractPreAuthenticatedProcessingFilter.class, order);
		order += STEP;
		filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter",
				order);
		order += STEP;
		put(UsernamePasswordAuthenticationFilter.class, order);
		order += STEP;
		put(ConcurrentSessionFilter.class, order);
		order += STEP;
		filterToOrder.put(
				"org.springframework.security.openid.OpenIDAuthenticationFilter", order);
		order += STEP;
		put(DefaultLoginPageGeneratingFilter.class, order);
		order += STEP;
		put(ConcurrentSessionFilter.class, order);
		order += STEP;
		put(DigestAuthenticationFilter.class, order);
		order += STEP;
		put(BasicAuthenticationFilter.class, order);
		order += STEP;
		put(RequestCacheAwareFilter.class, order);
		order += STEP;
		put(SecurityContextHolderAwareRequestFilter.class, order);
		order += STEP;
		put(JaasApiIntegrationFilter.class, order);
		order += STEP;
		put(RememberMeAuthenticationFilter.class, order);
		order += STEP;
		put(AnonymousAuthenticationFilter.class, order);
		order += STEP;
		put(SessionManagementFilter.class, order);
		order += STEP;
		put(ExceptionTranslationFilter.class, order);
		order += STEP;
		put(FilterSecurityInterceptor.class, order);
		order += STEP;
		put(SwitchUserFilter.class, order);
	}

 

3. Spring Security 的過濾器鏈是如何工做的

3.1 Spring Security 過濾器鏈是什麼 ?

        經過 debug 調試 能夠發如今 Spring Security 提供的過濾器中使用的 FilterChain 的實際類型是這個類 : org.springframework.security.web.FilterChainProxy.VirtualFilterChain 。它實現了 FilterChain 接口。maven

3.2 Spring Security 過濾器鏈初始化

        經過搜索能夠找到過濾器鏈條是在這個函數中進行初始化的 : org.springframework.security.config.annotation.web.builders.WebSecurity#performBuild 源碼:ide

@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<SecurityFilterChain>(
				chainSize);
		for (RequestMatcher ignoredRequest : ignoredRequests) {
			securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
		}
		for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
			securityFilterChains.add(securityFilterChainBuilder.build());
		}
		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;
	}

org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration#springSecurityFilterChain 源碼:FilterChainProxy 自己也是一個過濾器這個過濾器會被註冊到過濾器鏈上。而後這個過濾器內部封裝了 Spring Security 的過濾器鏈條。函數

@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);
		}
		return webSecurity.build();
	}

 

3.3 Spring Security 過濾器鏈的工做過程

org.springframework.security.web.FilterChainProxy#doFilter 源碼 :微服務

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);
		}
	}

org.springframework.security.web.FilterChainProxy#doFilterInternal 源碼 : 這個函數是 Spring Security 過濾器鏈條的執行入口。每次請求都會 new 一個 VirtualFilterChain 的實例對象,而後調用該對象的 doFilter 函數,因而請求就進入到 Spring Security 的過濾器鏈處理中。post

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);
	}

org.springframework.security.web.FilterChainProxy.VirtualFilterChain#doFilter 源碼 :這裏就是去挨個調用 Spring Security 的過濾器的過程 ,重點須要關注的是 originalChain (原始的過濾器鏈條也就是 servlet 容器的) , currentPosition  (spring security 過濾器鏈當前執行到的位置) , size (spring security 過濾器鏈中過濾器的個數) 。 當 currentPosition  == size 的時候也就意味着 spring security 的過濾器鏈條執行完了,因而就該使用原始的 originalChain 繼續去調用 servlet 容器中註冊的過濾器了。

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);
			}
		}
	}

 

3.4 運行時跳過 Spring Security 過濾器鏈的思路

        瞭解上面講的 spring security 過濾器鏈的執行過程後如何跳過spring security 的過濾器鏈就顯而易見了 , 只須要控制 org.springframework.security.web.FilterChainProxy.Virt    ualFilterChain 對象中的 currentPosition  == size 就能夠了。可是 org.springframework.security.web.FilterChainProxy.VirtualFilterChain 這個類是內部私有的靜態成員類。 Spring Security 的目的就是爲了封裝它將它隱藏起來,想一想也能夠理解畢竟這是它之因此能實現功能的核心,確定不但願被亂動。可是沒辦法爲了實現個人需求,我仍是要亂動它,想要改變它的話就只有經過反射的方式才能實現。

3.4.1 關於反射的小技巧

        Class.forName("org.springframework.security.web.FilterChainProxy.VirtualFilterChain"); 使用這行代碼的時候是不能成功獲取到 VirtualFilterChain 類的 Class 對象的。由於 Java 中內部類在編譯成 .class 文件後名稱是這樣的 FilterChainProxy$VirtualFilterChain 。想要成功獲取到這個內部類的 Class 對象的話須要這樣寫 Class.forName("org.springframework.security.web.FilterChainProxy$VirtualFilterChain");

3.5 運行時跳過 Spring Security 過濾器鏈的實現方式

        首先我自定義了一個過濾器而且把它加入到 Spring Security 過濾器鏈條中的最前面,由於個人目的是徹底的「關閉」掉spring security 的過濾器鏈。示例代碼 :經過反射的方式改變 currentPosition 的值便可。

@Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        if (! isSkipOver(request)) {
            filterChain.doFilter(request , response);
            return;
        }

        Class<? extends FilterChain> filterChainClass = filterChain.getClass();
        try {
            Class<?> virtualFilterChainClass =
                    Class.forName("org.springframework.security.web.FilterChainProxy$VirtualFilterChain");
            if (virtualFilterChainClass.isAssignableFrom(filterChainClass)) {
                Reflect reflect = Reflect.on(filterChain);
                Object size = reflect.field("size").get();
                reflect.set("currentPosition" , size);
            }
        } catch (Throwable t) {
            throw new ApplicationRuntimeException(t);
        }

        filterChain.doFilter(request , response);
    }

 

4. 廣告

        若是你也面臨到我所說的相似問題,須要編寫一些代碼來解決你的問題的話,那麼上面這些代碼已經不須要你再去花時間編寫了,我已經寫好了。你只須要經過 maven :

<dependency>
  <groupId>org.hepeng</groupId>
  <artifactId>hp-java-commons</artifactId>
  <version>1.1.3</version>
</dependency>

或者是 gradle :  implementation 'org.hepeng:hp-java-commons:1.1.3' 

再自定義一個 Filter 而且繼承 org.hepeng.commons.spring.security.web.filter.SkipOverSpringSecurityFilterChainFilter 便可。它已經通過了個人屢次測試能夠正常工做,若是你發現任何 bug 能夠向我反饋,我會盡快修復。

相關文章
相關標籤/搜索