SpringSecurity过滤器分析

SpringSecurity过滤器分析

WebAsyncManagerIntegrationFilter比较详细的使用可以看这篇或者SpringMVC执行流程,源码分析

该过滤器配合SpringMVC的异步模式,SecurityContext是和线程绑定在一起的并且是在SecurityContextPersistenceFilter被设置,当进入Controller返回结果需要异步执行时,会直接走DispatcherServlet和过滤器链,当前线程退出,response并不返回。
当异步有结果后,此请求会再被转发到DispatcherServlet(不经过滤器链)。如果异步执行时需要用到SecurityContext,必须将已经设置的SecurityContext保存到request域中的WebAsynvManager中的SecurityContextCallableProcessingInterceptor中,这个类比较简单,就是在异步处理前后准备好设置好SecurityContextHolder。
该过滤器的源码如下,逻辑十分简单。

@Override
protected void doFilterInternal(HttpServletRequest request, 
	HttpServletResponse response, FilterChain filterChain)
		throws ServletException, IOException {
	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
	SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager
			.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
	if (securityProcessingInterceptor == null) {
		/* 设置异步处理的拦截器 */
		asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,
				new SecurityContextCallableProcessingInterceptor());
	}
	filterChain.doFilter(request, response);
}

由于是为了配合SpringMVC,该过滤器无法禁用

SecurityContextPersistenceFilter

通过SecurityContextRepository加载、保存SecurityContext。默认是基于Session的,如需自定义context的管理,实习SecurityContextRepository接口即可。
其中的HttpSessionSecurityContextRepository基本帮我们做好了所有事情,我们只需要放心的设置、修改context、authentication,这些都会被其检测到,然后更新session,并保存。通过以下两个类完成监测:

  • SaveToSessionResponseWrapper
  • SaveToSessionRequestWrapper

SecurityContextPersistenceFilter逻辑比较简单,下面是默认的loadContext方法:

@Override
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
	HttpServletRequest request = requestResponseHolder.getRequest();
	HttpServletResponse response = requestResponseHolder.getResponse();
	HttpSession httpSession = request.getSession(false);
	SecurityContext context = readSecurityContextFromSession(httpSession);
	if (context == null) {
		context = generateNewContext();
		if (this.logger.isTraceEnabled()) {
			this.logger.trace(LogMessage.format("Created %s", context));
		}
	}
	/* 保存了当前response、request、session和context以实现检测 */ 
	SaveToSessionResponseWrapper wrappedResponse = new SaveToSessionResponseWrapper(response, request,
			httpSession != null, context);
	requestResponseHolder.setResponse(wrappedResponse);
	requestResponseHolder.setRequest(new SaveToSessionRequestWrapper(request, wrappedResponse));
	return context;
}

该过滤器可以禁用,但一般情况下我们可以选择实现SecurityContextRepository而不是重写过滤器。

HeaderWriterFilter

向当前响应添加响应头,像X-Frame-Options、X-XSS-Protection 和 X-Content-Type-Options,自实现也比较简单。
该过滤器可以被禁用。

CsrfFilter

csrf防护,一般情况下用现成的就行,可自定义csfrToken的管理方式,该过滤器可以被禁用。

LogoutFilter

用于实现登出的,较为简单。如果使用此过滤器登出,只需要实现LogoutHandler,LogoutSuccessHandler,请求不会到达Controller。

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
	if (requiresLogout(request, response)) {
		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
		if (this.logger.isDebugEnabled()) {
			this.logger.debug(LogMessage.format("Logging out [%s]", auth));
		}
		this.handler.logout(request, response, auth);
		this.logoutSuccessHandler.onLogoutSuccess(request, response, auth);
		return;
	}
	chain.doFilter(request, response);
}

UsernamePasswordAuthenticationFilter

该过滤器默认被禁用,除非了配置了formLogin,实际上用处也不大。用与用户名密码登录校验,生成authentication。
如果需要编写自己的AuthenticationFilter,这是个很好的参考。

DefaultLoginPageGeneratingFilter

配置了formLogin但没配置loginPage时用于生成默认登录页

DefaultLogoutPageGeneratingFilter

配置了formLogin没logoutPage时生成默认登出页

RememberMeAuthenticationFilter

配置rememberMe时使用

RequestCacheAwareFilter

请求缓存,主要逻辑在HttpSessionRequestCache中,可以自定义RequestCache,更改缓存行为。该过滤器可以被禁用。

在SpringSecurity中,缓存主要用于权限不足被重定向到认证流程,通过之后复用缓存请求,如果选择处理新请求,该过滤器没用

SecurityContextHolderAwareRequestFilter

将request再包装为Servlet3SecurityContextHolderAwareRequestWrapper,提供以下附加方法:
HttpServletRequest.authenticate(HttpServletResponse) - 允许用户确定他们是否通过身份验证,如果没有,则将用户发送到登录页面,通过AuthenticationEntryPoint实现。
HttpServletRequest.login(String, String) - 允许用户使用AuthenticationManager进行身份AuthenticationManager。
HttpServletRequest.logout() - 允许用户使用Spring Security中配置的LogoutHandler注销。
AsyncContext.start(Runnable) -自动复制SecurityContext从SecurityContextHolder对调用线程发现AsyncContext.start(Runnable)来处理该线程Runnable 。

Sevlet规范和该过滤器为操作request提供了更大的灵活性和扩展性。更多内容可以查看这里,如使用的都是原生HttpServletRequest,该过滤器可以被禁用。

AnonymousAuthenticationFilter

如果当前context的authentication为null,为当前context设置anonymousAuthentication,默认为:

/* key默认为启动时随机的uuid,可自定义 */
/* principal为anonymousUser */
/* authorities为ROLE_ANONYMOUS */
AnonymousAuthenticationToken token = new AnonymousAuthenticationToken(this.key, this.principal,
				this.authorities);

SessionManagementFilter

直接上源码

if (!this.securityContextRepository.containsContext(request)) {
	Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
	if (authentication != null && !this.trustResolver.isAnonymous(authentication)) {
		// The user has been authenticated during the current request, so call the
		// session strategy
		try {
			this.sessionAuthenticationStrategy.onAuthentication(authentication, request, response);
		}
		catch (SessionAuthenticationException ex) {
			// The session strategy can reject the authentication
			this.logger.debug("SessionAuthenticationStrategy rejected the authentication object", ex);
			SecurityContextHolder.clearContext();
			this.failureHandler.onAuthenticationFailure(request, response, ex);
			return;
		}
		// Eagerly save the security context to make it available for any possible
		// re-entrant requests which may occur before the current request
		// completes. SEC-1396.
		this.securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response);
	}
	else {
		// No security context or authentication present. Check for a session
		// timeout
		if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) {
			if (this.logger.isDebugEnabled()) {
				this.logger.debug(LogMessage.format("Request requested invalid session id %s",
						request.getRequestedSessionId()));
			}
			if (this.invalidSessionStrategy != null) {
				this.invalidSessionStrategy.onInvalidSessionDetected(request, response);
				return;
			}
		}
	}
	}
chain.doFilter(request, response);
}

下面讨论的是基于Session的时候。

  1. 如果当前请求的context尚未保存(此处的SecurityContextReposiry与上文为同一对象),那么该JSESSIONID是第一次请求服务器,直接放行;
  2. 如果不为空且authentication不为AnonymousAuthentication,可以自定义session的校验处理工作,默认什么也不做,然后保存上下文到repositry中
  3. 如果没有SecurityContext或者Authentication,检查session是否合法,若不合法,自定义后续动作,默认重定向,然后直接从当前过滤器返回,不放行。

如果没有检查校验session的需求,该过滤器可直接禁用。

ExceptionTranslationFilter

处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException
如果检测到AuthenticationException ,过滤器将启动authenticationEntryPoint,默认返回UNAUTHORIZED,401状态码
如果检测到AccessDeniedException ,过滤器将确定用户是否为匿名用户。如果他们是匿名用户,则将启动authenticationEntryPoint。如果他们不是匿名用户,过滤器将委托给AccessDeniedHandler。默认情况下,返回FOBBIDEN,403。

当用户需要访问某个页面但权限不足时,此过滤器捕获到异常,通过RequestCache保存当前请求,然后转到认证授权,通过之后,继续访问之前的页面,在RequestCacheAware中获取缓存的请求,继续处理。

如果在全局异常处理中配置了AuthenticationException和AccessDeniedException,该过滤器永远不会捕获到Controller层的异常

FilterSecurityInterceptor

通过前面的authentication判断用户是否有权限访问某个资源,一般情况下不需要改动

总结

无怪网友大多说SpringSecurity使用成本高,但是价值不大,许多功能看起来很鸡肋,添加的过滤器有点多,从一些过滤器来看,有前后端不分离时代的味道。
本人配置的SpringSecurity,几乎没剩下什么了,自己写几个Filter估计也不是太难,网友诚不欺我。

/* 自定义SecurityContextRepositry */
httpSecurity.securityContext().securityContextRepository(jwtSecurityContextRepositry);
/*不需要检测校验session*/
httpSecurity.sessionManagement().disable();
/* 使用HttpServlet,没有包装需求 */
httpSecurity.servletApi().disable();
/* 在全局处理器中处理异常 */
httpSecurity.exceptionHandling().disable();
/*登入登出在Controller中处理*/
httpSecurity.logout().disable();
/* 请求头不需要额外操作 */
httpSecurity.headers().disable();
/*重定向认证后重新处理请求*/
httpSecurity.requestCache().disable();
/* 不配置自动禁用 */
/*httpSecurity.formLogin();*/
/*httpSecurity.rememberMe();*/

最后还剩下不过五个SpringSecurity的Filter。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值