Spring Security 原理讲解

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

Spring Security 原理讲解_第1张图片  

 

如上图所示,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 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 additionalFilters;
		private final FirewalledRequest firewalledRequest;
		private final int size;
		private int currentPosition = 0;
 
		private VirtualFilterChain(FirewalledRequest firewalledRequest,
				FilterChain chain, List 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的加载过程

Spring Security 原理讲解_第2张图片

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,两者不相等,请求拒绝。 所以这个功能要视具体情况而用。

你可能感兴趣的:(Spring,Security,spring,spring,boot)