Spring Security 原理講解

一、整理了解下Spring Security 的工作原理   

  

 

如上圖所示,spring security 的主要工作就是在原有的網絡請求的過濾器鏈(ApplicationFilterChain)中額外添加一條過濾器鏈(FilterChainProxy),主要用於用戶認證與授權。

      請求進入web容器經一些容器自身的基礎加工後,進入到servlet的濾器鏈中,spring security 使用 DelegatingFilterProxy 這個filter,將 targetBeanName 設置爲 "springSecurityFilterChain" ,也就是security生成的 FilterChainProxy 這個類的bean的名字,使 servlet 過濾器鏈 指向了 spring security的過濾器鏈 ,同時也進入到spring 容器當中。經過一系列的認證工作,若認證成功後,會生成一個Authentication類型的對象(可以記錄當前登錄人的用戶信息,權限信息),放入到SecurityContext中(默認以ThreadLocal的方式保存),然後返回servlet 過濾器鏈中,向控制層前行。

        security的過濾器鏈會根據你所配置需要啓停的功能增減,同時你也可以創建自己的過濾器添加進這個過濾器鏈中。

 

二、FilterChainProxy源碼分析

@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		//此處是對請求是否第一次經過這個過濾器的判斷,在spring security中的過濾器中這種用法是相當常見的
		boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
		if (clearContext) {
			try {
				request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
				doFilterInternal(request, response, chain);
			}
			finally {
			//清除security上下文中的保存的Authentication對象
				SecurityContextHolder.clearContext();
				request.removeAttribute(FILTER_APPLIED);
			}
		}
		else {
			doFilterInternal(request, response, chain);
		}
	}

	
private void doFilterInternal(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		//對request進行一層包裝,同時校驗了一下請求的方式是否允許,以及請求地址中是否有非法字符,若不通過拋出異常
		FirewalledRequest fwRequest = firewall
				.getFirewalledRequest((HttpServletRequest) request);
		//對response進行了一層包裝,若在response中添加header或cookie時,會對header和cookie的一些屬性進行校驗
		HttpServletResponse fwResponse = firewall
				.getFirewalledResponse((HttpServletResponse) response);
		
		//獲取spring security中第一個匹配請求地址的過濾器列表
		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;
		}
		
		//將過濾器列表和servlet過濾器鏈包裝成一個新的過濾器鏈 VirtualFilterChain 是FilterChainProxy的內部類
		VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
		vfc.doFilter(fwRequest, fwResponse);
	}

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 {
			//security 過濾器執行結束後,繼續執行servlet過濾器鏈
			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() + "'");
				}
			//傳入this,nextFilter中的會繼續調用VirtualFilterChain.doFilter()直至所有過濾器執行結束或拋出異常
				nextFilter.doFilter(request, response, this);
			}
		}
	}

     FilterChainProxy過濾器通過內部的FilterChain,將請求引入到了security的過濾器鏈中,上述對其主要的方法進行了簡單描述。

 

三、基於SpringBoot,security的加載過程

springboot 啓動時 有關spring security 主要加載的類有SecurityAutoConfiguration、SecurityRequestMatcherProviderAutoConfiguration、UserDetailsServiceAutoConfiguration、SecurityFilterAutoConfiguration , 其中需要講一下SecurityAutoConfiguration 和SecurityFilterAutoConfiguration。

      1、 SecurityAutoConfiguration 引入了SpringBootWebSecurityConfiguration、WebSecurityEnablerConfiguration、SecurityDataConfiguration 這三個配置類:

        SpringBootWebSecurityConfiguration 當你沒有主動配置security時,這個類的作用會生效,生一套默認的配置

      WebSecurityEnablerConfiguration 的作用全在@EnableWebSecurity 這個註解上,使Security功能生效,並且承擔了其中最主要的初始化工作。

      @EnableWebSecurity 引入了 WebSecurityConfiguration類 ,在這個類中讀取了所有的security配置(可以配置多個spring security 過濾器鏈),並根據這些配置生成一個name爲“springSecurityFilterChain” 的bean,裏邊進行的動作相當多,有興趣的同學可以研讀一下這一塊源碼。

       SecurityDataConfiguration 屬於spring security 的一個擴展配置,此處省略。

     2、SecurityFilterAutoConfiguration 的作用只有一個,就是創建一個DelegatingFilterProxyRegistrationBean,將spring security 的過濾器鏈springSecurityFilterChain 與servlet過濾器相連。

 

四、注意事項

    1、當手動添加過濾器到spring security中時,如果這個過濾器添加到spring 容器中,spring 自動會將此過濾器添加到servlet過濾器鏈中,此時,兩條過濾器鏈都會有這個過濾器,則會執行兩次。  

        解決辦法:

           讓這個過濾器繼承OncePerRequestFilter ,這個過濾器內部保證只執行一次。

           或者不要將這個過濾器添加到spring容器中。

      2、在使用spring security 自帶的csrf防護功能時,在前端併發請求會導致認證不通過,原因是前端同時需要發起多個請求時,這幾個請求的發送總是會有一些先後順序,當第n(n>1)個請求準備發起時,獲取到的cookie中的csrfToken爲token1, 此時第一個請求的已響應,cookie中的csrfToken隨之替換成第一個請求中返回的cookie中的csrfToken2,這時第n個請求發出,header中攜帶的是token1,cookie中放的是token2,兩者不相等,請求拒絕。 所以這個功能要視具體情況而用。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章