SpringSecurity_过滤器链

过滤器链的加载流程
<!--认证授权过滤器-->
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <!--委派过滤器-->
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

进入DelegatingFilterProxy类,查看继承关系,可以看出它是个过滤器

public class DelegatingFilterProxy extends GenericFilterBean {}

public abstract class GenericFilterBean implements Filter, BeanNameAware, EnvironmentAware,
		EnvironmentCapable, ServletContextAware, InitializingBean, DisposableBean {}

核心方法

@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		// Lazily initialize the delegate if necessary.
		Filter delegateToUse = this.delegate;
		if (delegateToUse == null) {
			synchronized (this.delegateMonitor) {
				delegateToUse = this.delegate;
				if (delegateToUse == null) {
					WebApplicationContext wac = findWebApplicationContext();
					if (wac == null) {
						throw new IllegalStateException("No WebApplicationContext found: " +
								"no ContextLoaderListener or DispatcherServlet registered?");
					}
					delegateToUse = initDelegate(wac);
				}
				this.delegate = delegateToUse;
			}
		}
		// Let the delegate perform the actual doFilter operation.
		invokeDelegate(delegateToUse, request, response, filterChain);
	}

进入initDelegate方法,此处targetBeanName的值为springSecurityFilterChain,这个方法会返回一个filterChainProxy对象

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
		String targetBeanName = getTargetBeanName();
		Assert.state(targetBeanName != null, "No target bean name set");
		Filter delegate = wac.getBean(targetBeanName, Filter.class);
		if (isTargetFilterLifecycle()) {
			delegate.init(getFilterConfig());
		}
		return delegate;
	}

通过filterChainProxy对象找其所属的类
在这里插入图片描述
进入类中,找doFilter方法,内部有一个无论如何都会走doFilterInternal(request, response, chain);方法

@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);
				doFilterInternal(request, response, chain);
			}
			finally {
				SecurityContextHolder.clearContext();
				request.removeAttribute(FILTER_APPLIED);
			}
		}
		else {
			doFilterInternal(request, response, chain);
		}
	}

进入doFilterInternal(request, response, chain);方法,关键位置getFilters(fwRequest);

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

getFilters(fwRequest);方法会返回13个过滤器
在这里插入图片描述
至此委派过滤器作用到此结束。
下面一次讲述以下各过滤器的作用

  1. SecurityContextPersistenceFilter
    SecurityContextPersistenceFilter 主要是使用 SecurityContextRepository在 session 中保存或更新一个 SecurityContext,并将 SecurityContext 给以后的过滤器使用,来为后续 filter 建立所需的上下文。SecurityContext 中存储了当前用户的认证以及权限信息。
    简单的说,就是初始化一个类似于 springIOC 的容器。
  2. WebAsyncManagerIntegrationFilter
    此过滤器用于集成SecurityContext到Spring异步执行机制中的WebAsyncManager
    如果 springSecurity 要整合到 spring 工程里面去,必须使用这个过滤器。
  3. HeaderWriterFilter
    向请求的 Header 中添加相应的信息,可在 http 标签内部使用 security:headers 来控制
  4. CsrfFilter
    csrf 又称跨域请求伪造,SpringSecurity 会对所有 post 请求验证是否包含系统生成的 csrf 的 token 信息,如果不包含,则报错。起到防止 csrf 攻击的效果。
  5. logout.LogoutFilter
    匹配 URL 为/logout 的请求,实现用户退出,清除认证信息。
  6. UsernamePasswordAuthenticationFilter
    认证操作全靠这个过滤器,默认匹配 URL 为/login 且必须为 POST 请求。
  7. DefaultLoginPageGeneratingFilter(弃用)
    如果没有在配置文件中指定认证页面,则由该过滤器生成一个默认认证页面。
  8. DefaultLogoutPageGeneratingFilter(弃用)
    由此过滤器可以生产一个默认的退出登录页面
  9. BasicAuthenticationFilter
    此过滤器会自动解析 HTTP 请求中头部名字为 Authentication,且以 Basic 开头的头信息。
    简而言之就是解析 Http 请求头信息。
  10. RequestCacheAwareFilter
    通过 HttpSessionRequestCache 内部维护了一个 RequestCache,用于缓存HttpServletRequest
  11. SecurityContextHolderAwareRequestFilter
    针对 ServletRequest 进行了一次包装,使得 request 具有更加丰富的 API
  12. AnonymousAuthenticationFilter
    当 SecurityContextHolder 中认证信息为空,则会创建一个匿名用户存入到SecurityContextHolder 中。springsecurity 为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。
  13. SessionManagementFilter
    SecurityContextRepository 限制同一用户开启多个会话的数量
  14. ExceptionTranslationFilter
    异常转换过滤器位于整个 springSecurityFilterChain 的后方,用来转换整个链路中出现的异常。简单说就是,进行 springSecurity 中的异常处理。
  15. FilterSecurityInterceptor
    获取所配置资源访问的授权信息,根据 SecurityContextHolder 中存储的用户信息来决定其是否有权限。简单的说就是授权。

问:是不是 springsecurity 一共就这么多过滤器呢?
答案:不是!随着 spring-security.xml 配置的添加,还会出现新的过滤器。
问:是不是 springsecurity 每次都会加载这些过滤器呢?
答案:不是!随着 spring-security.xml 配置的修改,有些过滤器可能会被去掉。

CsrfFilter过滤器的底层原理

CsrfFilter类中doFilterInternal方法

  • 当request请求方式为trace / get / head / options时,直接放行;
  • 当request请求方式为post时,拦截请求,获取token,如果token存在,放行,不存在交给this.accessDeniedHandler.handle(request, response, exception);处理。
@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		request.setAttribute(HttpServletResponse.class.getName(), response);
		CsrfToken csrfToken = this.tokenRepository.loadToken(request);
		boolean missingToken = (csrfToken == null);
		if (missingToken) {
			csrfToken = this.tokenRepository.generateToken(request);
			this.tokenRepository.saveToken(csrfToken, request, response);
		}
		request.setAttribute(CsrfToken.class.getName(), csrfToken);
		request.setAttribute(csrfToken.getParameterName(), csrfToken);
		if (!this.requireCsrfProtectionMatcher.matches(request)) {
			if (this.logger.isTraceEnabled()) {
				this.logger.trace("Did not protect against CSRF since request did not match "
						+ this.requireCsrfProtectionMatcher);
			}
			filterChain.doFilter(request, response);
			return;
		}
		String actualToken = request.getHeader(csrfToken.getHeaderName());
		if (actualToken == null) {
			actualToken = request.getParameter(csrfToken.getParameterName());
		}
		if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
			this.logger.debug(
					LogMessage.of(() -> "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request)));
			AccessDeniedException exception = (!missingToken) ? new InvalidCsrfTokenException(csrfToken, actualToken)
					: new MissingCsrfTokenException(actualToken);
			this.accessDeniedHandler.handle(request, response, exception);
			return;
		}
		filterChain.doFilter(request, response);
	}
认证流程分析

还记得 UsernamePasswordAuthenticationFilter 这个过滤器吗,主要负责认证的过滤器
找到这个 UsernamePasswordAuthenticationFilter 这个类,核心方法attemptAuthentication

 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }

            username = username.trim();
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

进入authenticate(authRequest)方法,是个接口方法,找其实现类ProviderManager,是不是很熟悉?
在这里插入图片描述
ProviderManager 内部的authenticate方法,getProviders()获取所有的认证提供者,找一个合适的来处理,返回值是一个AuthenticationProvider(接口)对象,找其实现类AbstractUserDetailsAuthenticationProvider

public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		AuthenticationException parentException = null;
		Authentication result = null;
		Authentication parentResult = null;
		boolean debug = logger.isDebugEnabled();

		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}

			try {
				result = provider.authenticate(authentication);
				//...
			}
		}
///....		
	}

实现类AbstractUserDetailsAuthenticationProvider中,核心方法authenticate()中,关键方法retrieveUser

try {
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
			}

进入 retrieveUser 方法,是个抽象方法,找其实现类,DaoAuthenticationProvider中的retrieveUser方法,以下是关键位置

UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);

进入loadUserByUsername(username);方法,是一个 UserDetailsService 接口,返回值是UserDetails类型的接口,找UserDetails的实现类—>User,User内部有两个构造方法,完成认证过程。

public User(String username, String password,
			Collection<? extends GrantedAuthority> authorities) {
		this(username, password, true, true, true, true, authorities);
	}

public User(String username, String password, boolean enabled,
			boolean accountNonExpired, boolean credentialsNonExpired,
			boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
			//...
	}

所以,我们若是想要自定义认证,就必须实现UserDetailsService接口,重写 loadUserByUsername 方法,返回一个UserDetails对象,通过User构造函数完成认证。
方式一:
自定义一个类,实现UserDetailsService接口…
方式二:
使用UserService接口,继承UserDetailsService接口,使用UserServiceImpl实现UserService接口,重写方法

无论是那种方式,本质是没有区别的
区别点:

<!--    <bean id="authenticateManage" class="com.herd.aspect.UserInfoAuthenticateManage">-->
<!--    </bean>-->
<!--    <security:authentication-provider user-service-ref="authenticateManage">-->
       <security:authentication-provider user-service-ref="userServiceImpl">
       	
	   </security:authentication-provider>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值