spring Security源码分析-13种过滤器详解

发布时间:2024年01月16日

security过滤器创建流程和使用流程

请添加图片描述
在这里插入图片描述

在这里插入图片描述

感兴趣了解详细源码servlet过滤器执行security过滤器详细流程

security核心过滤器

spring security的13个核心过滤器(按执行顺序陈列):

  1. WebAsyncManagerIntegrationFilter
  2. SecurityContextPersistenceFilter
  3. HeaderWriterFilter
  4. LogoutFilter
  5. UsernamePasswordAuthenticationFilter
  6. DefaultLoginPageGeneratingFilter
  7. DefaultLogoutPageGeneratingFilter
  8. RequestCacheAwareFilter
  9. SecurityContextHolderAwareRequestFilter
  10. AnonymousAuthenticationFilter
  11. SessionManagementFilter
  12. ExceptionTranslationFilter
  13. FilterSecurityInterceptor

以上过滤器是通过FilterChainProxy的静态嵌套类VirtualFilterChain进行调用执行的
FilterChainProxy类
核心代码位置:org.springframework.security.web.FilterChainProxy#doFilterInternal

		VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
		vfc.doFilter(fwRequest, fwResponse);

VirtualFilterChain类
核心代码位置:org.springframework.security.web.FilterChainProxy.VirtualFilterChain#doFilter
在这里插入图片描述
使用的是责任链设计模式

WebAsyncManagerIntegrationFilter过滤器

public final class WebAsyncManagerIntegrationFilter extends OncePerRequestFilter {

	private static final Object CALLABLE_INTERCEPTOR_KEY = new Object();

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

}

在这里插入图片描述
主要功能:

  1. 创建WebAsyncManager
  2. 注册SecurityContextCallableProcessingInterceptor,具体使用位置org.springframework.web.servlet.mvc.method.annotation.AsyncTaskMethodReturnValueHandler#handleReturnValue
    属于mvc源码部分了,想进一步了解可以参考mvc源码。

SecurityContextPersistenceFilter过滤器

public class SecurityContextPersistenceFilter extends GenericFilterBean {

	static final String FILTER_APPLIED = "__spring_security_scpf_applied";

	private SecurityContextRepository repo;

	private boolean forceEagerSessionCreation = false;

	public SecurityContextPersistenceFilter() {
		this(new HttpSessionSecurityContextRepository());
	}

	public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
		this.repo = repo;
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
	}

	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		// ensure that filter is only applied once per request
		if (request.getAttribute(FILTER_APPLIED) != null) {
			chain.doFilter(request, response);
			return;
		}
		request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
		if (this.forceEagerSessionCreation) {
			HttpSession session = request.getSession();
			if (this.logger.isDebugEnabled() && session.isNew()) {
				this.logger.debug(LogMessage.format("Created session %s eagerly", session.getId()));
			}
		}
		HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
		SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
		try {
			SecurityContextHolder.setContext(contextBeforeChainExecution);
			if (contextBeforeChainExecution.getAuthentication() == null) {
				logger.debug("Set SecurityContextHolder to empty SecurityContext");
			}
			else {
				if (this.logger.isDebugEnabled()) {
					this.logger
							.debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
				}
			}
			chain.doFilter(holder.getRequest(), holder.getResponse());
		}
		finally {
			SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
			// Crucial removal of SecurityContextHolder contents before anything else.
			SecurityContextHolder.clearContext();
			this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
			request.removeAttribute(FILTER_APPLIED);
			this.logger.debug("Cleared SecurityContextHolder to complete request");
		}
	}

	public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
		this.forceEagerSessionCreation = forceEagerSessionCreation;
	}

}

主要功能:

  1. 通过SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);加载保存在session中的SecurityContext数据。
    我们知道客户端会将会话ID保存在cookie当中用于后端识别是否是同一个会话,所以当我们通过浏览器清除cookie中sessionID数据的时候,会导致后台找不到对应的session进而加载不到具体的SecurityContext数据,被security其他过滤器视为没有登录,跳转到登录页面。
    在这里插入图片描述

  2. 每次请求结束都会清除SecurityContext数据并将该数据保存到session,方便SecurityContext数据要么来自登录或者来自下次加载session数据,key为“SPRING_SECURITY_CONTEXT”,因此SecurityContext的生命周期是整个请求的生命周期。

			SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
			// Crucial removal of SecurityContextHolder contents before anything else.
			SecurityContextHolder.clearContext();
			this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());

HeaderWriterFilter过滤器

@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
					throws ServletException, IOException {

		HeaderWriterResponse headerWriterResponse = new HeaderWriterResponse(request,
				response, this.headerWriters);
		HeaderWriterRequest headerWriterRequest = new HeaderWriterRequest(request,
				headerWriterResponse);

		try {
			filterChain.doFilter(headerWriterRequest, headerWriterResponse);
		}
		finally {
			headerWriterResponse.writeHeaders();
		}
	}

主要功能:

  1. response对象重新封装,封装header配置集合
    ??① headerWriters包含:
    ??② contentTypeOptions
    ??③ xssProtection
    ??④ cacheControl
    ??⑤ hsts
    ??⑥ frameOptions
    ??⑦ hpkp
    ??⑧ contentSecurityPolicy
    ??⑨ referrerPolicy
    ??⑩ featurePolicy
    具体代码位置org.springframework.security.config.annotation.web.configurers.HeadersConfigurer#getHeaderWriters
	private List<HeaderWriter> getHeaderWriters() {
		List<HeaderWriter> writers = new ArrayList<>();
		addIfNotNull(writers, contentTypeOptions.writer);
		addIfNotNull(writers, xssProtection.writer);
		addIfNotNull(writers, cacheControl.writer);
		addIfNotNull(writers, hsts.writer);
		addIfNotNull(writers, frameOptions.writer);
		addIfNotNull(writers, hpkp.writer);
		addIfNotNull(writers, contentSecurityPolicy.writer);
		addIfNotNull(writers, referrerPolicy.writer);
		addIfNotNull(writers, featurePolicy.writer);
		writers.addAll(headerWriters);
		return writers;
	}

LogoutFilter过滤器

	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		if (requiresLogout(request, response)) {
			Authentication auth = SecurityContextHolder.getContext().getAuthentication();

			if (logger.isDebugEnabled()) {
				logger.debug("Logging out user '" + auth
						+ "' and transferring to logout destination");
			}

			this.handler.logout(request, response, auth);

			logoutSuccessHandler.onLogoutSuccess(request, response, auth);

			return;
		}

		chain.doFilter(request, response);
	}

主要功能:

  1. 拦截登出页请求/logout
  2. 清空SecurityContext ,handler的实例类是CompositeLogoutHandler
this.handler.logout(request, response, auth);

代码位置:org.springframework.security.web.authentication.logout.CompositeLogoutHandler#logout
在这里插入图片描述
代码位置:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler#logout

	public void logout(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) {
		Assert.notNull(request, "HttpServletRequest required");
		if (invalidateHttpSession) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				logger.debug("Invalidating session: " + session.getId());
				session.invalidate();
			}
		}

		if (clearAuthentication) {
			SecurityContext context = SecurityContextHolder.getContext();
			context.setAuthentication(null);
		}

		SecurityContextHolder.clearContext();
	}

3.跳转到登出页

logoutSuccessHandler.onLogoutSuccess(request, response, auth);

logoutSuccessHandler的实力类SimpleUrlLogoutSuccessHandler
代码位置:org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler#onLogoutSuccess

	public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException {
		super.handle(request, response, authentication);
	}

代码位置:org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler#determineTargetUrl
在这里插入图片描述
重定向到登录页
如果你想自定义logoutSuccessHandler.onLogoutSuccess(request, response, auth),可以通过修改LogoutConfigurer的logoutSuccessHandler,方式:

      http
                .logout()
                .logoutSuccessHandler((httpServletRequest, httpServletResponse, authentication) -> {
                    // 成功退出登录后返回200状态码
                    // httpServletResponse.setStatus(HttpServletResponse.SC_OK);
                    // 成功退出登录后的需要执行的代码写在这
                    System.out.println("123");
                });

原因:
org.springframework.security.config.annotation.web.configurers.LogoutConfigurer#createLogoutFilter

	private LogoutFilter createLogoutFilter(H http) throws Exception {
		logoutHandlers.add(contextLogoutHandler);
		LogoutHandler[] handlers = logoutHandlers
				.toArray(new LogoutHandler[logoutHandlers.size()]);
		LogoutFilter result = new LogoutFilter(getLogoutSuccessHandler(), handlers);
		result.setLogoutRequestMatcher(getLogoutRequestMatcher(http));
		result = postProcess(result);
		return result;
	}

getLogoutSuccessHandler方法()

	private LogoutSuccessHandler getLogoutSuccessHandler() {
		LogoutSuccessHandler handler = this.logoutSuccessHandler;
		if (handler == null) {
			handler = createDefaultSuccessHandler();
		}
		return handler;
	}

具体为什么修改LogoutConfigurer,请参考文章spring Security源码讲解-WebSecurityConfigurerAdapter

UsernamePasswordAuthenticationFilter过滤器

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {

		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		if (!requiresAuthentication(request, response)) {
			chain.doFilter(request, response);

			return;
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Request is to process authentication");
		}

		Authentication authResult;

		try {
			authResult = attemptAuthentication(request, response);
			if (authResult == null) {
				// return immediately as subclass has indicated that it hasn't completed
				// authentication
				return;
			}
			sessionStrategy.onAuthentication(authResult, request, response);
		}
		catch (InternalAuthenticationServiceException failed) {
			logger.error(
					"An internal error occurred while trying to authenticate the user.",
					failed);
			unsuccessfulAuthentication(request, response, failed);

			return;
		}
		catch (AuthenticationException failed) {
			// Authentication failed
			unsuccessfulAuthentication(request, response, failed);

			return;
		}

		// Authentication success
		if (continueChainBeforeSuccessfulAuthentication) {
			chain.doFilter(request, response);
		}

		successfulAuthentication(request, response, chain, authResult);
	}

主要功能:

  1. 校验是否是登录路径并且是post请求,如果不是就执行下一个过滤器
		if (!requiresAuthentication(request, response)) {
			chain.doFilter(request, response);

			return;
		}
  1. 进行登录校验(是否有效、登录次数、账号密码)等,因此我们可以在这个过滤器逻辑里面做一些定制,比如根据ip登录限制次数什么的。
authResult = attemptAuthentication(request, response);
  1. 登录
    ①如果登录失败,重定向到/login?error,
unsuccessfulAuthentication(request, response, failed);

在这里插入图片描述failureHandler的对象类SimpleUrlAuthenticationFailureHandler
代码位置:org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler#onAuthenticationFailure
在这里插入图片描述

②如果登录成功则改变登录前的sessionID,防止session会话固定攻攻击,默认是有状态session,不改变session对象,只改变sessionID,防止登录前和登陆后sessionID一致,导致被攻击。

sessionStrategy.onAuthentication(authResult, request, response);
  1. 保存登录authentication到SecurityContext
successfulAuthentication(request, response, chain, authResult);

DefaultLoginPageGeneratingFilter过滤器

	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		boolean loginError = isErrorPage(request);
		boolean logoutSuccess = isLogoutSuccess(request);
		if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
			String loginPageHtml = generateLoginPageHtml(request, loginError,
					logoutSuccess);
			response.setContentType("text/html;charset=UTF-8");
			response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
			response.getWriter().write(loginPageHtml);

			return;
		}

		chain.doFilter(request, response);
	}

主要功能:

  1. 处理登录失败或者登出成功,将登录页的html写回客户端
		boolean loginError = isErrorPage(request);
		boolean logoutSuccess = isLogoutSuccess(request);
		if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
			String loginPageHtml = generateLoginPageHtml(request, loginError,
					logoutSuccess);
			response.setContentType("text/html;charset=UTF-8");
			response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
			response.getWriter().write(loginPageHtml);

			return;
		}

在这里插入图片描述

DefaultLogoutPageGeneratingFilter过滤器

	@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		if (this.matcher.matches(request)) {
			renderLogout(request, response);
		} else {
			filterChain.doFilter(request, response);
		}
	}

这个过滤器优点特殊,默认不会执行 renderLogout(request, response);
因为登出会被LogoutFilter过滤器拦截并且重定向登录页/login?logout,因此this.matcher.matches(request)永远是false
如果不想要LogoutFilter登出逻辑,解决办法将LogoutConfigurer从HttpSecurity注销,方式:

        http
                // 关闭默认注销接口
                .logout().disable();

主要功能:
1.跳转登出页,通过页面进行注销

renderLogout(request, response);

在这里插入图片描述

RequestCacheAwareFilter过滤器

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {

		HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
				(HttpServletRequest) request, (HttpServletResponse) response);

		chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest,
				response);
	}

主要功能:
1.获取上次请求失败的目标路径,继续访问。
比如:匿名直接请求需要权限资源失败,ExceptionTranslationFilter过滤器会记录本次的目标请求,登录验证通过后会获取上次的目标请求继续请求。

		HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
				(HttpServletRequest) request, (HttpServletResponse) response);

代码位置: org.springframework.security.web.savedrequest.RequestCacheAwareFilter#doFilter

SecurityContextHolderAwareRequestFilter过滤器

	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		chain.doFilter(this.requestFactory.create((HttpServletRequest) req,
				(HttpServletResponse) res), res);
	}

this.requestFactory.create((HttpServletRequest),create方法属于HttpServlet3RequestFactory类.
代码位置:org.springframework.security.web.servletapi.HttpServlet3RequestFactory#create

	public HttpServletRequest create(HttpServletRequest request,
			HttpServletResponse response) {
		return new Servlet3SecurityContextHolderAwareRequestWrapper(request,
				this.rolePrefix, response);
	}

主要功能:
1.这个过滤器看起来很简单。目的仅仅是实现java ee中servlet api一些接口方法。
一些应用中直接使用getRemoteUser方法、isUserInRole方法,在使用spring security时其实就是通过这个过滤器来实现的。

AnonymousAuthenticationFilter过滤器

	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {

		if (SecurityContextHolder.getContext().getAuthentication() == null) {
			SecurityContextHolder.getContext().setAuthentication(
					createAuthentication((HttpServletRequest) req));

			if (logger.isDebugEnabled()) {
				logger.debug("Populated SecurityContextHolder with anonymous token: '"
						+ SecurityContextHolder.getContext().getAuthentication() + "'");
			}
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '"
						+ SecurityContextHolder.getContext().getAuthentication() + "'");
			}
		}

		chain.doFilter(req, res);
	}

主要功能:
1.当用户没有登录的时候创建一个用户名为anonymousUser,角色为ROLE_ANONYMOUS的匿名用户

			SecurityContextHolder.getContext().setAuthentication(
					createAuthentication((HttpServletRequest) req));

createAuthentication方法

	protected Authentication createAuthentication(HttpServletRequest request) {
		AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,
				principal, authorities);
		auth.setDetails(authenticationDetailsSource.buildDetails(request));

		return auth;
	}

在这里插入图片描述

SessionManagementFilter过滤器

	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		if (request.getAttribute(FILTER_APPLIED) != null) {
			chain.doFilter(request, response);
			return;
		}

		request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

		if (!securityContextRepository.containsContext(request)) {
			Authentication authentication = SecurityContextHolder.getContext()
					.getAuthentication();

			if (authentication != null && !trustResolver.isAnonymous(authentication)) {
				// The user has been authenticated during the current request, so call the
				// session strategy
				try {
					sessionAuthenticationStrategy.onAuthentication(authentication,
							request, response);
				}
				catch (SessionAuthenticationException e) {
					// The session strategy can reject the authentication
					logger.debug(
							"SessionAuthenticationStrategy rejected the authentication object",
							e);
					SecurityContextHolder.clearContext();
					failureHandler.onAuthenticationFailure(request, response, e);

					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.
				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 (logger.isDebugEnabled()) {
						logger.debug("Requested session ID "
								+ request.getRequestedSessionId() + " is invalid.");
					}

					if (invalidSessionStrategy != null) {
						invalidSessionStrategy
								.onInvalidSessionDetected(request, response);
						return;
					}
				}
			}
		}

		chain.doFilter(request, response);
	}

主要功能:
1.校验用户是否没有被持久化过(保存到session),如果没有就持久化一次

				securityContextRepository.saveContext(SecurityContextHolder.getContext(),
						request, response);

2.校验session是否过期

				if (request.getRequestedSessionId() != null
						&& !request.isRequestedSessionIdValid()) {

ExceptionTranslationFilter过滤器

	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		try {
			chain.doFilter(request, response);

			logger.debug("Chain processed normally");
		}
		catch (IOException ex) {
			throw ex;
		}
		catch (Exception ex) {
			// Try to extract a SpringSecurityException from the stacktrace
			Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
			RuntimeException ase = (AuthenticationException) throwableAnalyzer
					.getFirstThrowableOfType(AuthenticationException.class, causeChain);

			if (ase == null) {
				ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
						AccessDeniedException.class, causeChain);
			}

			if (ase != null) {
				if (response.isCommitted()) {
					throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
				}
				handleSpringSecurityException(request, response, chain, ase);
			}
			else {
				// Rethrow ServletExceptions and RuntimeExceptions as-is
				if (ex instanceof ServletException) {
					throw (ServletException) ex;
				}
				else if (ex instanceof RuntimeException) {
					throw (RuntimeException) ex;
				}

				// Wrap other Exceptions. This shouldn't actually happen
				// as we've already covered all the possibilities for doFilter
				throw new RuntimeException(ex);
			}
		}
	}

主要功能:
1.处理FilterSecurityInterceptor过滤器权限校验抛出的异常,如果权限不通过则接受FilterSecurityInterceptor抛出的异常并处理
2.记录访问异常时本次请求

handleSpringSecurityException

代码位置:org.springframework.security.web.access.ExceptionTranslationFilter#handleSpringSecurityException

	private void handleSpringSecurityException(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain, RuntimeException exception)
			throws IOException, ServletException {
		if (exception instanceof AuthenticationException) {
			logger.debug(
					"Authentication exception occurred; redirecting to authentication entry point",
					exception);

			sendStartAuthentication(request, response, chain,
					(AuthenticationException) exception);
		}
		else if (exception instanceof AccessDeniedException) {
			Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
			if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
				logger.debug(
						"Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",
						exception);

				sendStartAuthentication(
						request,
						response,
						chain,
						new InsufficientAuthenticationException(
							messages.getMessage(
								"ExceptionTranslationFilter.insufficientAuthentication",
								"Full authentication is required to access this resource")));
			}
			else {
				logger.debug(
						"Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
						exception);

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

sendStartAuthentication方法

	protected void sendStartAuthentication(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain,
			AuthenticationException reason) throws ServletException, IOException {
		// SEC-112: Clear the SecurityContextHolder's Authentication, as the
		// existing Authentication is no longer considered valid
		SecurityContextHolder.getContext().setAuthentication(null);
		requestCache.saveRequest(request, response);
		logger.debug("Calling Authentication entry point.");
		authenticationEntryPoint.commence(request, response, reason);
	}

requestCache.saveRequest(request, response);记录本次请求

FilterSecurityInterceptor过滤器

	public void invoke(FilterInvocation fi) throws IOException, ServletException {
		if ((fi.getRequest() != null)
				&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
				&& observeOncePerRequest) {
			// filter already applied to this request and user wants us to observe
			// once-per-request handling, so don't re-do security checking
			fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
		}
		else {
			// first time this request being called, so perform security checking
			if (fi.getRequest() != null && observeOncePerRequest) {
				fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
			}

			InterceptorStatusToken token = super.beforeInvocation(fi);

			try {
				fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
			}
			finally {
				super.finallyInvocation(token);
			}

			super.afterInvocation(token, null);
		}
	}

核心代码

InterceptorStatusToken token = super.beforeInvocation(fi);

代码位置:org.springframework.security.access.intercept.AbstractSecurityInterceptor#beforeInvocation

	protected InterceptorStatusToken beforeInvocation(Object object) {
		Assert.notNull(object, "Object was null");
		final boolean debug = logger.isDebugEnabled();

		if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
			throw new IllegalArgumentException(
					"Security invocation attempted for object "
							+ object.getClass().getName()
							+ " but AbstractSecurityInterceptor only configured to support secure objects of type: "
							+ getSecureObjectClass());
		}

		Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
				.getAttributes(object);

		if (attributes == null || attributes.isEmpty()) {
			if (rejectPublicInvocations) {
				throw new IllegalArgumentException(
						"Secure object invocation "
								+ object
								+ " was denied as public invocations are not allowed via this interceptor. "
								+ "This indicates a configuration error because the "
								+ "rejectPublicInvocations property is set to 'true'");
			}

			if (debug) {
				logger.debug("Public object - authentication not attempted");
			}

			publishEvent(new PublicInvocationEvent(object));

			return null; // no further work post-invocation
		}

		if (debug) {
			logger.debug("Secure object: " + object + "; Attributes: " + attributes);
		}

		if (SecurityContextHolder.getContext().getAuthentication() == null) {
			credentialsNotFound(messages.getMessage(
					"AbstractSecurityInterceptor.authenticationNotFound",
					"An Authentication object was not found in the SecurityContext"),
					object, attributes);
		}

		Authentication authenticated = authenticateIfRequired();

		// Attempt authorization
		try {
			this.accessDecisionManager.decide(authenticated, object, attributes);
		}
		catch (AccessDeniedException accessDeniedException) {
			publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
					accessDeniedException));

			throw accessDeniedException;
		}

		if (debug) {
			logger.debug("Authorization successful");
		}

		if (publishAuthorizationSuccess) {
			publishEvent(new AuthorizedEvent(object, attributes, authenticated));
		}

		// Attempt to run as a different user
		Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
				attributes);

		if (runAs == null) {
			if (debug) {
				logger.debug("RunAsManager did not change Authentication object");
			}

			// no further work post-invocation
			return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
					attributes, object);
		}
		else {
			if (debug) {
				logger.debug("Switching to RunAs Authentication: " + runAs);
			}

			SecurityContext origCtx = SecurityContextHolder.getContext();
			SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
			SecurityContextHolder.getContext().setAuthentication(runAs);

			// need to revert to token.Authenticated post-invocation
			return new InterceptorStatusToken(origCtx, true, attributes, object);
		}
	}

主要功能:
1.获取路径和角色的映射关系

		Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
				.getAttributes(object);

如我们在这里配置的映射数据
在这里插入图片描述
2.是否每次都要重新登录一次用户(开启的话,可以防止用户此时过期,但是性能降低,默认不开启,只在UserNamepasswordFilter过滤器中进行登录)

Authentication authenticated = authenticateIfRequired();

3.校验当前用户访问的路径是否满足该路径配置权限

this.accessDecisionManager.decide(authenticated, object, attributes);

过程:
1.获取当前登录用户的角色
在这里插入图片描述
2.根据当前登录object,从attributes中拿到需要当前用户拥有的角色集合
3.判断当前登录用户信息authenticated中的authorities属性是否存在访问路径必要必要集合元素,如果存在就通过,否则无权限抛AccessDeniedException异常被FilterSecurityInterceptor过滤器捕获。
因此我们可以在FilterSecurityInterceptor过程中实现自定义权限校验逻辑

需要两不操作:

	Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
				.getAttributes(object);

需要改变FilterSecurityInterceptor过滤器的俩个实例对象securityMetadataSource和accessDecisionManager
实现代码:

@DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
@Configuration
public class MyConfig{

    @Autowired
    private FilterChainProxy filterChainProxy;
    @PostConstruct
    public void postConstruct() {
        List<SecurityFilterChain> securityFilterChainList = filterChainProxy.getFilterChains();
        List<Filter> filters = securityFilterChainList.get(0).getFilters();
        for(Filter filter : filters){
            if(filter instanceof FilterSecurityInterceptor){
                FilterSecurityInterceptor filterSecurityInterceptor = (FilterSecurityInterceptor) filter;
                filterSecurityInterceptor.setSecurityMetadataSource(new MyFilterInvocationSecurityMetadataSource());
                filterSecurityInterceptor.setAccessDecisionManager(new MyAccessDecisionManager());
            }
        }

    }

    public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource{

        @Override
        public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
            System.out.println("加载路径所需角色");
            return null;
        }

        @Override
        public Collection<ConfigAttribute> getAllConfigAttributes() {
            return null;
        }

        @Override
        public boolean supports(Class<?> clazz) {
            return false;
        }
    }

    public class MyAccessDecisionManager implements AccessDecisionManager{

        @Override
        public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
            System.out.println("进行权限校验");
        }

        @Override
        public boolean supports(ConfigAttribute attribute) {
            return false;
        }

        @Override
        public boolean supports(Class<?> clazz) {
            return false;
        }
    }

}
文章来源:https://blog.csdn.net/sunboylife/article/details/135557289
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。