13种核心过滤器
spring security的13个核心过滤器(按执行顺序陈列):
- WebAsyncManagerIntegrationFilter
- SecurityContextPersistenceFilter
- HeaderWriterFilter
- LogoutFilter
- UsernamePasswordAuthenticationFilter
- DefaultLoginPageGeneratingFilter
- DefaultLogoutPageGeneratingFilter
- RequestCacheAwareFilter
- SecurityContextHolderAwareRequestFilter
- AnonymousAuthenticationFilter
- SessionManagementFilter
- ExceptionTranslationFilter
- FilterSecurityInterceptor
security过滤器创建流程
感兴趣了解详细源码servlet过滤器执行security过滤器详细流程
security过滤器执行流程
过滤器介绍
WebAsyncManagerIntegrationFilter
public final class WebAsyncManagerIntegrationFilter extends OncePerRequestFilter {private static final Object CALLABLE_INTERCEPTOR_KEY = new Object();@Overrideprotected 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);}}
主要功能:
- 创建WebAsyncManager
- 注册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;}@Overridepublic 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 requestif (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;}}
主要功能:
-
通过SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);加载保存在session中的SecurityContext数据。
我们知道客户端会将会话ID保存在cookie当中用于后端识别是否是同一个会话,所以当我们通过浏览器清除cookie中sessionID数据的时候,会导致后台找不到对应的session进而加载不到具体的SecurityContext数据,被security其他过滤器视为没有登录,跳转到登录页面。
-
每次请求结束都会清除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
@Overrideprotected 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();}}
主要功能:
- 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);}
主要功能:
- 拦截登出页请求/logout
- 清空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// authenticationreturn;}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 failedunsuccessfulAuthentication(request, response, failed);return;}// Authentication successif (continueChainBeforeSuccessfulAuthentication) {chain.doFilter(request, response);}successfulAuthentication(request, response, chain, authResult);}
主要功能:
- 校验是否是登录路径并且是post请求,如果不是就执行下一个过滤器
if (!requiresAuthentication(request, response)) {chain.doFilter(request, response);return;}
- 进行登录校验(是否有效、登录次数、账号密码)等,因此我们可以在这个过滤器逻辑里面做一些定制,比如根据ip登录限制次数什么的。
authResult = attemptAuthentication(request, response);
- 登录
①如果登录失败,重定向到/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);
- 保存登录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);}
主要功能:
- 处理登录失败或者登出成功,将登录页的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
@Overrideprotected 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 strategytry {sessionAuthenticationStrategy.onAuthentication(authentication,request, response);}catch (SessionAuthenticationException e) {// The session strategy can reject the authenticationlogger.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// timeoutif (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 stacktraceThrowable[] 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-isif (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 doFilterthrow 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 validSecurityContextHolder.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 checkingfi.getChain().doFilter(fi.getRequest(), fi.getResponse());}else {// first time this request being called, so perform security checkingif (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 authorizationtry {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 userAuthentication runAs = this.runAsManager.buildRunAs(authenticated, object,attributes);if (runAs == null) {if (debug) {logger.debug("RunAsManager did not change Authentication object");}// no further work post-invocationreturn 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-invocationreturn 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{@Autowiredprivate FilterChainProxy filterChainProxy;@PostConstructpublic 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{@Overridepublic Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {System.out.println("加载路径所需角色");return null;}@Overridepublic Collection<ConfigAttribute> getAllConfigAttributes() {return null;}@Overridepublic boolean supports(Class<?> clazz) {return false;}}public class MyAccessDecisionManager implements AccessDecisionManager{@Overridepublic void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {System.out.println("进行权限校验");}@Overridepublic boolean supports(ConfigAttribute attribute) {return false;}@Overridepublic boolean supports(Class<?> clazz) {return false;}}}
这样我们就可以动态加载和校验权限,不用在WebSecurityConfigurerAdapter里配置了