栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

源码系列 — Spring Security之过滤器篇

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

源码系列 — Spring Security之过滤器篇

Spring Security之过滤器篇
  • SecurityFilterChain
  • FilterChainProxy
  • 内置SecurityFilter
    • ChannelProcessingFilter
    • WebAsyncManagerIntegrationFilter
    • SecurityContextPersistenceFilter
    • HeaderWriterFilter
    • CorsFilter
    • CsrfFilter
    • LogoutFilter
    • UsernamePasswordAuthenticationFilter
    • ConcurrentSessionFilter
    • BearerTokenAuthenticationFilter
    • BasicAuthenticationFilter
    • RequestCacheAwareFilter
    • RememberMeAuthenticationFilter
    • AnonymousAuthenticationFilter
    • SessionManagementFilter
    • ExceptionTranslationFilter
    • FilterSecurityInterceptor

SecurityFilterChain

讲过滤器[Security Filter]之前,首先要提到SecurityFilterChain。

public interface SecurityFilterChain {
	boolean matches(HttpServletRequest request);
	List getFilters();
}

SecurityFilterChain包含了两个方法。

  • matches(HttpServletRequest request):主要是提供给FilterChainProxy进行请求的路由。
  • List getFilters(): 提供SecurityFilterChain中的Security Filter。
FilterChainProxy

FilterChainProxy继承于GenericFilterBean,是spring容器中的一个过滤器。

它主要做了两个工作:

  • 内置了一个防火强HttpFirewall。对ServletRequest中的HttpMethod、ContextPath、Hostname、uri等做了限制。具体内容可以查看StrictHttpFirewall::getFirewalledRequest。
  • 执行内部的SecurityFilterChain,直到SecurityFilterChain中的Security Filter执行完毕后,继续执行Servlet Container中的剩余的Filter。详见FilterChainProxy.VirtualFilterChain::doFilter:
		
		public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
		    //如果SecurityFilterChain中的最后一个SecurityFilter执行完成,则继续执行原Servlet容器中的过滤器 
			if (this.currentPosition == this.size) {
				if (logger.isDebugEnabled()) {
					logger.debug(LogMessage.of(() -> "Secured " + requestLine(this.firewalledRequest)));
				}
				// Deactivate path stripping as we exit the security filter chain
				this.firewalledRequest.reset();
				this.originalChain.doFilter(request, response);
				return;
			}
			//SecurityFilterChain标识位移动到下个
			this.currentPosition++;
			//获取SecurityFilterChain中的下一个Security Filter
			Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Invoking %s (%d/%d)", nextFilter.getClass().getSimpleName(),
						this.currentPosition, this.size));
			}
			//执行获取的这个Security Filter
			nextFilter.doFilter(request, response, this);
		}
内置SecurityFilter ChannelProcessingFilter

ChannelProcessingFilter主要负责检测当前请求的安全通道secure channel是否符合配置要求。

主要有三种配置:

  • REQUIRES_INSECURE_CHANNEL: 当前请求没有使用了安全通道,例如https请求。
  • REQUIRES_SECURE_CHANNEL: 当前请求使用了安全通道,例如http请求。
  • ANY_CHANNEL: 没有限制。

配置用法举例:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
        		//访问/admin-api,需要以https开头的方式访问
                .antMatchers("/admin-api").access("REQUIRES_SECURE_CHANNEL")
                //访问/actuator,不受安全通道限制
                .antMatchers("/actuator").access("ANY_CHANNEL");

    }
WebAsyncManagerIntegrationFilter

WebAsyncManagerIntegrationFilter主要是用于集成WebAsyncManager,支持在异步线程中管理SecurityContext。

SecurityContextPersistenceFilter

SecurityContextPersistenceFilter主要实现了对SecurityContext的在Session中的持久化和存取功能。

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		
		HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
		//HttpSessionSecurityContextRepository 从Session中获取SecurityContext
		SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
		try {
			//把SecurityContext注入到SecurityContextHolder,SecurityContextHolder中三种SecurityContextHolderStrategy策略,详见下文。
			SecurityContextHolder.setContext(contextBeforeChainExecution);
			
			chain.doFilter(holder.getRequest(), holder.getResponse());
		}
		finally {
			SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
			//删除线程中的SecurityContext
			SecurityContextHolder.clearContext();
			//持久化到SecurityContextRepository
			this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
			
		}
	}

默认提供了3种对SecurityContext线程持有策略,可以通过设置系统参数spring.security.strategy来实现切换:

  • MODE_THREADLOCAL: 只能获取当前线程的SecurityContext。
  • MODE_INHERITABLETHREADLOCAL: 能够获取父线程和当前线程的SecurityContext。
  • MODE_GLOBAL: 能够获全局应用的SecurityContext。
HeaderWriterFilter

HeaderWriterFilter通过一系列HeaderWriter,往HttpServletResponse的Http Header中写入值。可以通过设置shouldWriteHeadersEagerly,来控制前置写入还是后置写入。

CorsFilter

CorsFilter通过配置CorsConfiguration,设置CORS Http Header。

@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
			FilterChain filterChain) throws ServletException, IOException {
		//从CorsConfigurationSource中获取CorsConfiguration
		CorsConfiguration corsConfiguration = this.configSource.getCorsConfiguration(request);
		//CorsProcessor通过CorsConfiguration对Response进行设置。
		boolean isValid = this.processor.processRequest(corsConfiguration, request, response);
		//如果处理不成功或者当前请求是一个CORS pre-flight,则阻止当前请求继续执行。
		if (!isValid || CorsUtils.isPreFlightRequest(request)) {
			return;
		}
		filterChain.doFilter(request, response);
	}
CsrfFilter

CsrfFilter用于防止CSRF攻击。

CSRF 英文全称是 Cross-site request forgery,所以又称为“跨站请求伪造”,是指黑客引诱用户打开黑客的网站,在黑客的网站中,利用用户的登录状态发起的跨站请求。简单来讲,CSRF 攻击就是黑客利用了用户的登录状态,并通过第三方的站点来做一些坏事

Spring Security 防止CSRF攻击的方法:

  • 在浏览器向服务器发起请求时,服务器生成一个 CSRF Token。CSRF Token 其实就是服务器生成的字符串,然后将该字符串植入到返回的页面中。
  • 在浏览器端如果要发起转账的请求,那么需要带上页面中的 CSRF Token,然后服务器会验证该 Token 是否合法。如果是从第三方站点发出的请求,那么将无法获取到 CSRF Token 的值,所以即使发出了请求,服务器也会因为 CSRF Token 不正确而拒绝请求。
@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		request.setAttribute(HttpServletResponse.class.getName(), response);
		//从Token库中获取CsrfToken(支持CsrfToke持久化在cookie、session或自定义库的中)
		CsrfToken csrfToken = this.tokenRepository.loadToken(request);
		boolean missingToken = (csrfToken == null);
		if (missingToken) {
			//第一次请求需要生成一个CsrfToken,并持久化到Token库。
			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);
	}

配置用法举例:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
        		//访问/admin-api,需要以https开头的方式访问
                .antMatchers("/admin-api").access("REQUIRES_SECURE_CHANNEL")
                //访问/actuator,不受安全通道限制
                .antMatchers("/actuator").access("ANY_CHANNEL");

    }
LogoutFilter

CsrfFilter处理登出请求。

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));
			}
			//LogoutHandler处理登出操作
			this.handler.logout(request, response, auth);
			//LogoutSuccessHandler处理成功登出后的操作
			this.logoutSuccessHandler.onLogoutSuccess(request, response, auth);
			return;
		}
		chain.doFilter(request, response);
	}

配置用法举例:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
       			http.authorizeRequests(authorizeRequests ->
	   				authorizeRequests.antMatchers("

				}
				catch (AuthenticationException authenticationException) {
					

					//登录失败处理,主要做的是清空Cookie
					rememberMeServices.loginFail(request, response);
					onUnsuccessfulAuthentication(request, response,
							authenticationException);
				}
			}

			chain.doFilter(request, response);
		}
		else {
			

			chain.doFilter(request, response);
		}
	}

再看下RememberMeAuthenticationFilter::doFilter中的核心逻辑方法RememberMeServices::autoLogin

@Override
	public final Authentication autoLogin(HttpServletRequest request,
			HttpServletResponse response) {
		//提取Cookie中的信息
		String rememberMeCookie = extractRememberMeCookie(request);

		

		UserDetails user = null;

		try {
			//解析信息,获取一个UserDetails
			String[] cookieTokens = decodeCookie(rememberMeCookie);
			user = processAutoLoginCookie(cookieTokens, request, response);
			userDetailsChecker.check(user);

			logger.debug("Remember-me cookie accepted");

			//通过UserDetails生成一个RememberMeAuthenticationToken
			return createSuccessfulAuthentication(request, user);
		}
		catch (CookieTheftException cte) {
			
		}
		
		cancelCookie(request, response);
		return null;
	}
AnonymousAuthenticationFilter

AnonymousAuthenticationFilter对未认证的请求,会封装成一个匿名AnonymousAuthenticationToken。

SessionManagementFilter

SessionManagementFilter主要是对Session相关的管理,例如,防止会话固定攻击Session Fixation Attack,支持多端登录踢下线等功能。

ExceptionTranslationFilter

ExceptionTranslationFilter 用于处理 AccessDeniedException 和 AuthenticationException的异常。

private void handleSpringSecurityException(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain, RuntimeException exception)
			throws IOException, ServletException {
		if (exception instanceof AuthenticationException) {
			

			sendStartAuthentication(request, response, chain,
					(AuthenticationException) exception);
		}
		else if (exception instanceof AccessDeniedException) {
			Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
			if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
				

				sendStartAuthentication(
						request,
						response,
						chain,
						new InsufficientAuthenticationException(
							messages.getMessage(
								"ExceptionTranslationFilter.insufficientAuthentication",
								"Full authentication is required to access this resource")));
			}
			else {
				

				accessDeniedHandler.handle(request, response,
						(AccessDeniedException) exception);
			}
		}
	}
FilterSecurityInterceptor

FilterSecurityInterceptor主要负责授权Authorization任务。

public void invoke(FilterInvocation fi) throws IOException, ServletException {
		if ((fi.getRequest() != null)
				&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
				&& observeOncePerRequest) {
			fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
		}
		else {
			
			//主要是通过AccessDecisionManager::decide 来进行授权工作。
			InterceptorStatusToken token = super.beforeInvocation(fi);

			try {
				fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
			}
			finally {
				//把Token中的SecurityContext去刷新SecurityContextHolder中的SecurityContext
				super.finallyInvocation(token);
			}
			//用AfterInvocationManager::decide方法进行后置授权工作。
			super.afterInvocation(token, null);
		}
	}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/885300.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号