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

13种核心过滤器

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

security过滤器创建流程

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

在这里插入图片描述
感兴趣了解详细源码servlet过滤器执行security过滤器详细流程

security过滤器执行流程

请添加图片描述

过滤器介绍

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

}

这样我们就可以动态加载和校验权限,不用在WebSecurityConfigurerAdapter里配置了
在这里插入图片描述

无论你的梦想有多么高远,记住,一切皆有可能

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/325558.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Windows Server 2019配置DNS服务器

正文共&#xff1a;1234 字 31 图&#xff0c;预估阅读时间&#xff1a;1 分钟 我们在给Windows Server添加角色和功能时&#xff0c;会发现有一项“远程桌面服务安装”&#xff0c;它的介绍为“为虚拟桌面基础结构&#xff08;Virtual Desktop Infrastructure&#xff0c;VDI&…

java处理16进制字符串的一些方法和基础知识

前言&#xff1a;本篇文章是对于基础数据的处理的一些简单经验总结里边包含了一些基础的数据储存和数据转化的一些知识&#xff0c;同样也包含有部分快捷的数据处理方法。主要用于个人知识的一个记录和方便进行对应的数据转换和处理。 1、bit,字节和字的关系 1.1 bit和字节的…

怎么注册微商城?开启微商城之旅

在这个数字化时代&#xff0c;微商城的出现为商家提供了一个全新的机会&#xff0c;商家企业可以通过微商城来展示和销售自己的产品。而对于一些商家而言&#xff0c;不知道怎么注册微商城。下面给大家做一个简单的分享。 第一步&#xff1a;选择合适的微商城搭建工具 在注册…

移动通信系统关键技术多址接入OFDM学习(7)

1.OFDM是一种多载波传输方案&#xff0c;可以将高速串行传输转换为低速并行传输&#xff0c;增加符号持续时间&#xff0c;抗多径干扰能力强。 串行和并行有着不同的比特持续时间&#xff0c;同时拥有相同的数据速率。因此&#xff0c;虽然OFDM将串行信号转换为并行信号&#…

【好书推荐-第四期】《Go专家编程(第2版)》华为资深技术专家力作,第1版评分9.4,适合Go程序员面试

&#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主、前后端开发、人工智能研究生。公粽号&#xff1a;程序员洲洲。 &#x1f388; 本文专栏&#xff1a;本文…

Dynamic Coarse-to-Fine Learning for Oriented Tiny Object Detection(CVPR2023)

文章目录 -Abstract & Conclusion现存问题解决方法结论 -问题方法Overviewaha&#xff0c;our work Dynamic PriorCoarse Prior MatchingFiner Dynamic Posterior Matching hh 源代码 - 该论文作者此前提出了RFLA&#xff0c;一种基于高斯接受场的标签分配策略 动态先验&a…

使用HTTP/2在Linux上的Nginx服务器进行优化

随着互联网的发展&#xff0c;HTTP/2协议逐渐成为主流。与传统的HTTP/1.1相比&#xff0c;HTTP/2提供了更高的传输效率和更好的安全性。在Linux上使用Nginx服务器进行优化&#xff0c;我们可以充分利用HTTP/2的优势&#xff0c;提高网站的性能和用户体验。 1. 安装Nginx并启用…

tessreact训练字库

tessreact主要用于字符识别&#xff0c;除了使用软件自带的中英文识别库&#xff0c;还可以使用Tesseract OCR训练属于自己的字库。 一、软件环境搭建 使用Tesseract OCR训练自己的字库&#xff0c;需要安装Tesseract OCR和jTessBoxEditor(配套训练工具)。jTessBoxEditor需要…

[ACM 学习] 最长上升子序列

LIS&#xff08;最长上升子序列&#xff09;的三种经典求法 - 一只不咕鸟 - 博客园 (cnblogs.com) 理解一下第三种方法&#xff08;贪心二分查找&#xff09; 因为构建的是上升子序列&#xff0c;所以是可以用二分查找找到最大的小于当前 A[i] 的在子序列中的 F[j]&#xff0…

【数据结构和算法】奇偶链表

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、题目描述 二、题解 2.1 方法一&#xff1a;分离节点后合并 三、代码 3.1 方法一&#xff1a;分离节点后合并 四、复杂度分…

CC工具箱使用指南:【提取特定文字】

一、简介 有时候我们会遇到一些混杂着各种中文、英文、数字、特殊符号的文字&#xff0c;需要对其进行提纯&#xff0c;如下图所示&#xff1a; 或者做规划的人应该做过一件事&#xff0c;从CAD测绘图中可以读取到类似【混3】、【砖2】的文字&#xff0c;如果想要从中提取出层…

Linux反向、分离解析与主从复制

前言 上篇介绍了DNS正向解析&#xff0c;本文将继续介绍反向解析与主从复制等内容。域名反向解析即从IP地址到域名的映射。为了完成逆向域名解析&#xff0c;系统提供一个特别域&#xff0c;该特别域称为逆向解析域。 目录 前言 一、反向解析 1. 配置bind服务 2. 修改区…

2024年甘肃省职业院校技能大赛信息安全管理与评估 样题一 理论题

竞赛需要完成三个阶段的任务&#xff0c;分别完成三个模块&#xff0c;总分共计 1000分。三个模块内容和分值分别是&#xff1a; 1.第一阶段&#xff1a;模块一 网络平台搭建与设备安全防护&#xff08;180 分钟&#xff0c;300 分&#xff09;。 2.第二阶段&#xff1a;模块二…

《江苏联通数据安全体系建设》入选“星河”优秀案例

近日&#xff0c;国家信息通信研究院和中国通信标准化协会大数据技术标准推进委员会&#xff08;CCSA TC601&#xff09;共同举办的第七届大数据“星河(Galaxy)”案例征集活动结果公布&#xff0c;江苏联通与天空卫士联合申报的《江苏联通数据安全体系建设》案例&#xff0c;被…

基于springboot的疫情物资捐赠和分配系统

&#x1f345;点赞收藏关注 → 私信领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345;一 、设计说明 1.1 课题背景 二…

vtk9.3 + Visual Studio2019 + Cmake3.28 win11 上的环境安装(这个过程网上比较多,自己记录下过程加深下印象)

开始 介绍 欢迎来到 VTK&#xff01;我们建议您首先阅读《VTK book》&#xff0c;这是一本全面的 VTK 指南&#xff0c;涵盖了其功能的所有方面。此外&#xff0c;您可能会发现探索 VTK 示例很有帮助&#xff0c;这是一组有用的参考资料&#xff0c;演示了如何使用 VTK 的不同模…

数学建模-预测人口数据

目录 中国09~18年人口数据 创建时间 绘制时间序列图 使用专家建模器 得到结果 预测结果 残差的白噪声检验 中国09~18年人口数据 创建时间 路径&#xff1a;数据-> 定义日期和时间 绘制时间序列图 使用专家建模器 看看spss最终判断是那个模型最佳的契合 得到结果 预…

Pandas十大练习题,掌握常用方法

文章目录 Pandas分析练习题1. 获取并了解数据2. 数据过滤与排序3. 数据分组4. Apply函数5. 合并数据6. 数据统计7. 数据可视化8. 创建数据框9. 时间序列10. 删除数据 代码均在Jupter Notebook上完成 Pandas分析练习题 数据集可从此获取&#xff1a; 链接: https://pan.baidu.co…

使用WAF防御网络上的隐蔽威胁之SQL注入攻击

SQL注入攻击是一种普遍存在且危害巨大的网络安全威胁&#xff0c;它允许攻击者通过执行恶意的SQL语句来操纵或破坏数据库。 这种攻击不仅能够读取敏感数据&#xff0c;还可能用于添加、修改或删除数据库中的记录。因此&#xff0c;了解SQL注入攻击的机制及其防御策略对于保护网…

YOLOv5姿态估计:HRnet实时检测人体关键点

前言&#xff1a; Hello大家好&#xff0c;我是Dream。 今天来学习一下利用YOLOv5进行姿态估计&#xff0c;HRnet与SimDR检测图片、视频以及摄像头中的人体关键点&#xff0c;欢迎大家一起前来探讨学习~ 本文目录&#xff1a; 一、项目准备1Pycharm中克隆github上的项目2.具体步…