前言

在上篇文档中,我们通过添加注解的方式,就实现了权限控制。接下来分析下源码,了解他的执行流程。

首先以下面代码问控制入口。

    @PreAuthorize("hasRole('ROLE_ROOT')")public Object test() {SecurityContext context = SecurityContextHolder.getContext();Authentication authentication = context.getAuthentication();MyUser principal = (MyUser) authentication.getPrincipal();return principal;}

授权流程图:

源码分析

核心类

FilterSecurityInterceptor

​FilterSecurityInterceptor 作为 Spring Security Filter Chain 的最后一个 Filter,承担着非常重要的作用。如获取当前 request 对应的权限配置,调用访问控制器进行鉴权操作等,都是核心功能。进行访问控制的处理就在这个过滤器中。

ConfigAttribute

ConfigAttribute是一个接口,其作用为存储相关访问控制的规则。

public interface ConfigAttribute extends Serializable {String getAttribute();
}

而这些访问规则可以通过配置类或者注解方式来配置。

比如HttpSecurity的默认配置为所有的请求都需要被认证,也可以添加自定义的访问控制规则

    protected void configure(HttpSecurity http) throws Exception {this.logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");http.authorizeRequests((requests) -> {((AuthorizedUrl)requests.anyRequest()).authenticated();});http.formLogin();http.httpBasic();}

比如可以通过注解方式进行访问控制:

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")

WebExpressionConfigAttribute是ConfigAttribute的常用实现类,并实现了EvaluationContextPostProcessor接口,他的主要作用是存储我们配置的表达式及其处理器。

/*** Simple expression configuration attribute for use in web request authorizations.** @author Luke Taylor* @since 3.0* 用于 Web 请求授权的简单表达式配置属性。*/
class WebExpressionConfigAttribute implements ConfigAttribute, EvaluationContextPostProcessor<FilterInvocation> {// 表达式 比如private final Expression authorizeExpression;// el表达式内容处理处理器private final EvaluationContextPostProcessor<FilterInvocation> postProcessor;WebExpressionConfigAttribute(Expression authorizeExpression,EvaluationContextPostProcessor<FilterInvocation> postProcessor) {this.authorizeExpression = authorizeExpression;this.postProcessor = postProcessor;}

FilterInvocation

FilterInvocation类,主要是保存与 HTTP 过滤器关联的对象,包括请求、响应、连接器链。

public class FilterInvocation {static final FilterChain DUMMY_CHAIN = (req, res) -> {throw new UnsupportedOperationException("Dummy filter chain");};private FilterChain chain;private HttpServletRequest request;private HttpServletResponse response;public FilterInvocation(ServletRequest request, ServletResponse response, FilterChain chain) {Assert.isTrue(request != null && response != null && chain != null, "Cannot pass null values to constructor");this.request = (HttpServletRequest) request;this.response = (HttpServletResponse) response;this.chain = chain;}
}

InterceptorStatusToken

InterceptorStatusToken是FilterSecurityInterceptor拦截器beforeInvocation处理返回的对象,主要包含了反映了安全拦截的状态,最终afterInvocation方法会对他进行最终的处理。

public class InterceptorStatusToken {private SecurityContext securityContext;private Collection<ConfigAttribute> attr;private Object secureObject;private boolean contextHolderRefreshRequired;public InterceptorStatusToken(SecurityContext securityContext, boolean contextHolderRefreshRequired,Collection<ConfigAttribute> attributes, Object secureObject) {this.securityContext = securityContext;this.contextHolderRefreshRequired = contextHolderRefreshRequired;this.attr = attributes;this.secureObject = secureObject;}
}

RunAsManager

RunAsManager是一个接口,主要定义了对Authentication进行替换的方法。在极少数情况下,用户可以使用不同的Authentication替换SecurityContext中的Authentication。

public interface RunAsManager {Authentication buildRunAs(Authentication authentication, Object object, Collection<ConfigAttribute> attributes);boolean supports(ConfigAttribute attribute);boolean supports(Class<?> clazz);
}

AccessDecisionManager

AccessDecisionManager是访问决策管理器,做出最终的访问控制(授权)决定,他是一个接口,有众多的实现类。每一个具体的实现类可以表示为一种决策。

public interface AccessDecisionManager {/*** Resolves an access control decision for the passed parameters.* @param authentication the caller invoking the method (not null)* @param object the secured object being called* @param configAttributes the configuration attributes associated with the secured* object being invoked* @throws AccessDeniedException if access is denied as the authentication does not* hold a required authority or ACL privilege* @throws InsufficientAuthenticationException if access is denied as the* authentication does not provide a sufficient level of trust* 为传递的参数解析访问控制决策 */void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)throws AccessDeniedException, InsufficientAuthenticationException;/*** Indicates whether this <code>AccessDecisionManager</code> is able to process* authorization requests presented with the passed <code>ConfigAttribute</code>.* <p>* This allows the <code>AbstractSecurityInterceptor</code> to check every* configuration attribute can be consumed by the configured* <code>AccessDecisionManager</code> and/or <code>RunAsManager</code> and/or* <code>AfterInvocationManager</code>.* </p>* @param attribute a configuration attribute that has been configured against the* <code>AbstractSecurityInterceptor</code>* @return true if this <code>AccessDecisionManager</code> can support the passed* configuration attribute* 指示此 <code>AccessDecisionManager</code> 是否能够处理通过传递的 <code>ConfigAttribute</code> 呈现的授权请求*/boolean supports(ConfigAttribute attribute);/*** Indicates whether the <code>AccessDecisionManager</code> implementation is able to* provide access control decisions for the indicated secured object type.* @param clazz the class that is being queried* @return <code>true</code> if the implementation can process the indicated class* 指示 <code>AccessDecisionManager</code> 实现是否能够为指示的安全对象类型提供访问控制决策。*/boolean supports(Class<?> clazz);}

AffirmativeBased

AffirmativeBased是AccessDecisionManager默认的实现类,AffirmativeBased的逻辑是这样的:

   (1)只要有AccessDecisionVoter的投票为ACCESS_GRANTED则同意用户进行访问;(2)如果全部弃权也表示通过;(3)如果没有一个人投赞成票,但是有人投反对票,则将抛出AccessDeniedException。
public class AffirmativeBased extends AbstractAccessDecisionManager {public AffirmativeBased(List<AccessDecisionVoter<?>> decisionVoters) {super(decisionVoters);}@Override@SuppressWarnings({"rawtypes", "unchecked"})public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)throws AccessDeniedException {int deny = 0; // 否决票// 1. 循环WEB 投票器for (AccessDecisionVoter voter : getDecisionVoters()) {// 2. 投票,返回结果int result = voter.vote(authentication, object, configAttributes);//switch (result) {// 为1 ,表示通过,有一个通过,则直接通过,return退出函数,不抛出异常case AccessDecisionVoter.ACCESS_GRANTED:return;// -1 ,拒绝访问,否决票+1case AccessDecisionVoter.ACCESS_DENIED:deny++;break;default:break;}}// 如果否决票》0 抛出AccessDeniedExceptionif (deny > 0) {throw new AccessDeniedException(this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));}// To get this far, every AccessDecisionVoter abstained// 3. 没有否决票,也没有赞成票,都弃权了,则检查是否允许如果所有弃权决定。checkAllowIfAllAbstainDecisions();}
}

AccessDecisionVoter

AccessDecisionVoter就表示为投票决策者,也就是一个投票器了。AccessDecisionVoter是一个接口,定义了三个常量,表示投票结果。

/** *  访问决策选民, 表示一个类负责对授权决定进行投票*/
public interface AccessDecisionVoter<S> {int ACCESS_GRANTED = 1; // 授予访问权限int ACCESS_ABSTAIN = 0; // 访问弃权int ACCESS_DENIED = -1; // 拒绝访问boolean supports(ConfigAttribute attribute);boolean supports(Class<?> clazz);// 投票int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);}

WebExpressionVoter

AccessDecisionManager 默认的投票器 WebExpressionVoter。其实,就是对使用 http.authorizeRequests() 基于 Spring-EL进行控制权限的的授权决策类。


/*** Voter which handles web authorisation decisions.** @author Luke Taylor* @since 3.0* 处理web授权决定的选民*/
public class WebExpressionVoter implements AccessDecisionVoter<FilterInvocation> {private final Log logger = LogFactory.getLog(getClass());// 默认的WebSecurity授权表达式处理器private SecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();// 投票@Overridepublic int vote(Authentication authentication, FilterInvocation filterInvocation,Collection<ConfigAttribute> attributes) {// 1. 校验参数Assert.notNull(authentication, "authentication must not be null");Assert.notNull(filterInvocation, "filterInvocation must not be null");Assert.notNull(attributes, "attributes must not be null");// 2. 获取http配置项WebExpressionConfigAttribute webExpressionConfigAttribute = findConfigAttribute(attributes);// 3. 没有配置规则,弃权if (webExpressionConfigAttribute == null) {this.logger.trace("Abstained since did not find a config attribute of instance WebExpressionConfigAttribute");return ACCESS_ABSTAIN;}// 4. 对EL表达式进行处理EvaluationContext ctx = webExpressionConfigAttribute.postProcess(this.expressionHandler.createEvaluationContext(authentication, filterInvocation), filterInvocation);boolean granted = ExpressionUtils.evaluateAsBoolean(webExpressionConfigAttribute.getAuthorizeExpression(), ctx);if (granted) {// 5. 符合条件,赞成票return ACCESS_GRANTED;}this.logger.trace("Voted to deny authorization");// 6. 最后都没有则反对票return ACCESS_DENIED;}private WebExpressionConfigAttribute findConfigAttribute(Collection<ConfigAttribute> attributes) {for (ConfigAttribute attribute : attributes) {if (attribute instanceof WebExpressionConfigAttribute) {return (WebExpressionConfigAttribute) attribute;}}return null;}@Overridepublic boolean supports(ConfigAttribute attribute) {return attribute instanceof WebExpressionConfigAttribute;}@Overridepublic boolean supports(Class<?> clazz) {return FilterInvocation.class.isAssignableFrom(clazz);}public void setExpressionHandler(SecurityExpressionHandler<FilterInvocation> expressionHandler) {this.expressionHandler = expressionHandler;}}

SecurityExpressionHandler

Web安全表达式处理器(handler)。最后对EL访问控制表达式的处理,都是在此接口中的实现类进行。它的默认实现类为DefaultWebSecurityExpressionHandler。这个类的主要作用是创建SecurityExpressionRoot对象。

public class DefaultWebSecurityExpressionHandler extends AbstractSecurityExpressionHandler<FilterInvocation>implements SecurityExpressionHandler<FilterInvocation> {private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();// 角色前缀private String defaultRolePrefix = "ROLE_";@Overrideprotected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,FilterInvocation fi) {WebSecurityExpressionRoot root = new WebSecurityExpressionRoot(authentication, fi);root.setPermissionEvaluator(getPermissionEvaluator());root.setTrustResolver(this.trustResolver);root.setRoleHierarchy(getRoleHierarchy());root.setDefaultRolePrefix(this.defaultRolePrefix);return root;}
}

WebSecurityExpressionRoot

WebSecurityExpressionRoot实现了SecurityExpressionRoot接口,其顶级接口为SecurityExpressionOperations。

SecurityExpressionOperations接口中定义个很多方法,每个方法都关联着一个访问控制表达式。

/*** Standard interface for expression root objects used with expression-based security.** @author Andrei Stefan* @author Luke Taylor* @since 3.1.1* 与基于表达式的安全性一起使用的表达式根对象的标准接口*/
public interface SecurityExpressionOperations {/*** Gets the {@link Authentication} used for evaluating the expressions* @return the {@link Authentication} for evaluating the expressions*/Authentication getAuthentication();/*** Determines if the {@link #getAuthentication()} has a particular authority within* {@link Authentication#getAuthorities()}.* @param authority the authority to test (i.e. "ROLE_USER")* @return true if the authority is found, else false*/boolean hasAuthority(String authority);/*** Determines if the {@link #getAuthentication()} has any of the specified authorities* within {@link Authentication#getAuthorities()}.* @param authorities the authorities to test (i.e. "ROLE_USER", "ROLE_ADMIN")* @return true if any of the authorities is found, else false*/boolean hasAnyAuthority(String... authorities);/*** <p>* Determines if the {@link #getAuthentication()} has a particular authority within* {@link Authentication#getAuthorities()}.* </p>* <p>* This is similar to {@link #hasAuthority(String)} except that this method implies* that the String passed in is a role. For example, if "USER" is passed in the* implementation may convert it to use "ROLE_USER" instead. The way in which the role* is converted may depend on the implementation settings.* </p>* @param role the authority to test (i.e. "USER")* @return true if the authority is found, else false*/boolean hasRole(String role);/*** <p>* Determines if the {@link #getAuthentication()} has any of the specified authorities* within {@link Authentication#getAuthorities()}.* </p>* <p>* This is a similar to hasAnyAuthority except that this method implies that the* String passed in is a role. For example, if "USER" is passed in the implementation* may convert it to use "ROLE_USER" instead. The way in which the role is converted* may depend on the implementation settings.* </p>* @param roles the authorities to test (i.e. "USER", "ADMIN")* @return true if any of the authorities is found, else false*/boolean hasAnyRole(String... roles);/*** Always grants access.* @return true*/boolean permitAll();/*** Always denies access* @return false*/boolean denyAll();/*** Determines if the {@link #getAuthentication()} is anonymous* @return true if the user is anonymous, else false*/boolean isAnonymous();/*** Determines ifthe {@link #getAuthentication()} is authenticated* @return true if the {@link #getAuthentication()} is authenticated, else false*/boolean isAuthenticated();/*** Determines if the {@link #getAuthentication()} was authenticated using remember me* @return true if the {@link #getAuthentication()} authenticated using remember me,* else false*/boolean isRememberMe();/*** Determines if the {@link #getAuthentication()} authenticated without the use of* remember me* @return true if the {@link #getAuthentication()} authenticated without the use of* remember me, else false*/boolean isFullyAuthenticated();/*** Determines if the {@link #getAuthentication()} has permission to access the target* given the permission* @param target the target domain object to check permission on* @param permission the permission to check on the domain object (i.e. "read",* "write", etc).* @return true if permission is granted to the {@link #getAuthentication()}, else* false*/boolean hasPermission(Object target, Object permission);/*** Determines if the {@link #getAuthentication()} has permission to access the domain* object with a given id, type, and permission.* @param targetId the identifier of the domain object to determine access* @param targetType the type (i.e. com.example.domain.Message)* @param permission the perission to check on the domain object (i.e. "read",* "write", etc)* @return true if permission is granted to the {@link #getAuthentication()}, else* false*/boolean hasPermission(Object targetId, String targetType, Object permission);}

WebSecurityExpressionRoot可以理解为权限表达式对应的JAVA类,他除了接口中的表达式支持,还添加了一个hasIpAddress方法,可以对IP进行控制。

public class WebSecurityExpressionRoot extends SecurityExpressionRoot {/*** Allows direct access to the request object*/public final HttpServletRequest request;public WebSecurityExpressionRoot(Authentication a, FilterInvocation fi) {super(a);this.request = fi.getRequest();}/*** Takes a specific IP address or a range using the IP/Netmask (e.g. 192.168.1.0/24 or* 202.24.0.0/14).* @param ipAddress the address or range of addresses from which the request must* come.* @return true if the IP address of the current request is in the required range.*/public boolean hasIpAddress(String ipAddress) {IpAddressMatcher matcher = new IpAddressMatcher(ipAddress);return matcher.matches(this.request);}}

流程分析

1. 进入ExceptionTranslationFilter

首先会进入ExceptionTranslationFilter的doFilter方法,之前说过这个过滤器的主要作用是拦截异常并处理。

 private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws IOException, ServletException {try {// 1. 请求直接放行chain.doFilter(request, response);} catch (IOException ex) {throw ex;} catch (Exception ex) {// Try to extract a SpringSecurityException from the stacktrace// 2. 捕获后续出现的异常Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);if (securityException == null) {securityException = (AccessDeniedException) this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);}if (securityException == null) {rethrow(ex);}if (response.isCommitted()) {throw new ServletException("Unable to handle the Spring Security Exception "+ "because the response is already committed.", ex);}// 3. 处理发生的异常handleSpringSecurityException(request, response, chain, securityException);}}

2. 进入FilterSecurityInterceptor

接下来进入FilterSecurityInterceptor过滤器,他的doFilter方法,调用的是自身的invoke(FilterInvocation filterInvocation)方法。该方法完成了整个访问控制逻辑。

 /***  doFilter实际执行的方法* @param filterInvocation 封装了request response 过滤器链的对象*/public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {// 1. 如果已经执行过该过滤器,直接放行if (isApplied(filterInvocation) && this.observeOncePerRequest) {// filter already applied to this request and user wants us to observe// once-per-request handling, so don't re-do security checkingfilterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());return;}// first time this request being called, so perform security checking// 2. 第一次调用这个请求,所以执行安全检查if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {// 3. 在request中添加__spring_security_filterSecurityInterceptor_filterApplied = true,表示执行了该过滤器filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);}// 4. 前置访问控制处理InterceptorStatusToken token = super.beforeInvocation(filterInvocation);try {// 5. 放行        filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());} finally {super.finallyInvocation(token);}// 6. 后置处理super.afterInvocation(token, null);}

3. 进入AbstractSecurityInterceptor

在FilterSecurityInterceptor中,会调用父类的beforeInvocation(filterInvocation)方法进行处理,最终返回一个InterceptorStatusToken对象,它就是spring security处理鉴权的入口。

 protected InterceptorStatusToken beforeInvocation(Object object) {Assert.notNull(object, "Object was null");// 1. 判断object是不是FilterInvocationif (!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());}// 2. 获取配置的访问控制规则 any request =》authenticated ,没有配置,return nullCollection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);if (CollectionUtils.isEmpty(attributes)) {Assert.isTrue(!this.rejectPublicInvocations,() -> "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 (this.logger.isDebugEnabled()) {this.logger.debug(LogMessage.format("Authorized public object %s", object));}publishEvent(new PublicInvocationEvent(object));return null; // no further work post-invocation}// 3. 判断认证对象Authentication是否为nullif (SecurityContextHolder.getContext().getAuthentication() == null) {credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound","An Authentication object was not found in the SecurityContext"), object, attributes);}// 4. 获取Authentication对象Authentication authenticated = authenticateIfRequired();if (this.logger.isTraceEnabled()) {this.logger.trace(LogMessage.format("Authorizing %s with attributes %s", object, attributes));}// Attempt authorization// 5. 进行授权判断attemptAuthorization(object, attributes, authenticated);if (this.logger.isDebugEnabled()) {this.logger.debug(LogMessage.format("Authorized %s with attributes %s", object, attributes));}// 6. 发布授权成功if (this.publishAuthorizationSuccess) {publishEvent(new AuthorizedEvent(object, attributes, authenticated));}// Attempt to run as a different user// 7. 对Authentication进行再处理,这里没有处理,直接返回nullAuthentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);if (runAs != null) {SecurityContext origCtx = SecurityContextHolder.getContext();SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());SecurityContextHolder.getContext().setAuthentication(runAs);if (this.logger.isDebugEnabled()) {this.logger.debug(LogMessage.format("Switched to RunAs authentication %s", runAs));}// need to revert to token.Authenticated post-invocationreturn new InterceptorStatusToken(origCtx, true, attributes, object);}this.logger.trace("Did not switch RunAs authentication since RunAsManager returned null");// no further work post-invocation// 8. 返回InterceptorStatusTokenreturn new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);}

在beforeInvocation方法中的核心方法为attemptAuthorization,它会调用授权管理器进行决策,当失败发生异常时,会爆出异常。

 /*** 授权判断** @param object        filter invocation [GET /test]* @param attributes 配置的URL放行、需要验证路径等配置* @param authenticated 认证对象*/private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes,Authentication authenticated) {try {// 1. 调用授权管理器进行决策this.accessDecisionManager.decide(authenticated, object, attributes);} catch (AccessDeniedException ex) {// 2. 访问被拒绝。抛出AccessDeniedException异常if (this.logger.isTraceEnabled()) {this.logger.trace(LogMessage.format("Failed to authorize %s with attributes %s using %s", object,attributes, this.accessDecisionManager));} else if (this.logger.isDebugEnabled()) {this.logger.debug(LogMessage.format("Failed to authorize %s with attributes %s", object, attributes));}// 3. 发布授权失败事件publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, ex));throw ex;}}

4. 决策者进行投票

调用授权管理器进行决策,会进入默认的决策器AffirmativeBased,上面说过它的投票机制,这里获取到的选民只有一个。

循环投票者,并开始计票。

5. 开始投票

进入WebExpressionVoter的vote方法开始投票。

 // 投票@Overridepublic int vote(Authentication authentication, FilterInvocation filterInvocation,Collection<ConfigAttribute> attributes) {// 1. 校验参数Assert.notNull(authentication, "authentication must not be null");Assert.notNull(filterInvocation, "filterInvocation must not be null");Assert.notNull(attributes, "attributes must not be null");// 2. 获取http配置项WebExpressionConfigAttribute webExpressionConfigAttribute = findConfigAttribute(attributes);// 3. 没有配置规则,弃权if (webExpressionConfigAttribute == null) {this.logger.trace("Abstained since did not find a config attribute of instance WebExpressionConfigAttribute");return ACCESS_ABSTAIN;}// 4. 对EL表达式进行处理EvaluationContext ctx = webExpressionConfigAttribute.postProcess(this.expressionHandler.createEvaluationContext(authentication, filterInvocation), filterInvocation);boolean granted = ExpressionUtils.evaluateAsBoolean(webExpressionConfigAttribute.getAuthorizeExpression(), ctx);if (granted) {// 5. 符合条件,赞成票return ACCESS_GRANTED;}this.logger.trace("Voted to deny authorization");// 6. 最后都没有则反对票return ACCESS_DENIED;}

6. 对表达式进行处理

投票中的核心代码为:

            EvaluationContext ctx = webExpressionConfigAttribute.postProcess(this.expressionHandler.createEvaluationContext(authentication, filterInvocation), filterInvocation);boolean granted = ExpressionUtils.evaluateAsBoolean(webExpressionConfigAttribute.getAuthorizeExpression(), ctx);

首先会创建EL表达式的上下文。

this.expressionHandler.createEvaluationContext(authentication, filterInvocation)

然后调用ExpressionUtils工具类对EL表达式进行处理,最终调用的是SpelExpression中的getValue方法。

第一次是对配置类中的规则进行校验,这里是anyRequest()).authenticated(),因为登录了,所以这个投票是通过的。

第二次是对我们配置了权限注解的方法进行校验。
会首先获取到我们请求方法上的EL表达式,然后进行配置校验,涉及到EL的相关知识,这里后续介绍。

表达式检验之后,这个当前用户有这个角色,所以投票通过,加下来就要进行授权成功处理了。

7. 授权成功处理、

没有抛出异常,则认为授权通过,FilterSecurityInterceptor会进入finallyInvocation方法。这个方法主要是判断需不需要重新设置 SecurityContext内容,这里没有配置,直接跳过。

    protected void finallyInvocation(InterceptorStatusToken token) {if (token != null && token.isContextHolderRefreshRequired()) {SecurityContextHolder.setContext(token.getSecurityContext());if (this.logger.isDebugEnabled()) {this.logger.debug(LogMessage.of(() -> {return "Reverted to original authentication " + token.getSecurityContext().getAuthentication();}));}}}

接下来进入后置处理afterInvocation方法,再次调用了finallyInvocation方法,然后查询是否还有决策后置处理器,如果有,再次进行决策。最后的最后,才代表授权成功,就交由Spring MVC ,访问到我们的controller方法了。

    protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {if (token == null) {return returnedObject;} else {this.finallyInvocation(token);if (this.afterInvocationManager != null) {try {returnedObject = this.afterInvocationManager.decide(token.getSecurityContext().getAuthentication(), token.getSecureObject(), token.getAttributes(), returnedObject);} catch (AccessDeniedException var4) {this.publishEvent(new AuthorizationFailureEvent(token.getSecureObject(), token.getAttributes(), token.getSecurityContext().getAuthentication(), var4));throw var4;}}return returnedObject;}}

Spring Security系列(7)-访问控制流程源码解析相关推荐

  1. Spring Security Core 5.1.2 源码解析 -- PasswordEncoderFactories

    概述 PasswordEncoderFactories是Spring Security创建DelegatingPasswordEncoder对象的工厂类.该工厂所创建的DelegatingPasswo ...

  2. Spring Security系列教程03--创建SpringSecurity项目

    前言 在上一章节中,一一哥 已经带大家认识了Spring Security,对其基本概念已有所了解,但是作为一个合格的程序员,最关键的肯定还是得动起手来,所以从本篇文章开始,我就带大家搭建第一个Spr ...

  3. Spring Security系列之Spring Social实现微信社交登录(九)

    社交登录又称作社会化登录(Social Login),是指网站的用户可以使用腾讯QQ.人人网.开心网.新浪微博.搜狐微博.腾讯微博.淘宝.豆瓣.MSN.Google等社会化媒体账号登录该网站. 前言 ...

  4. spring security系列一:架构概述

    一直以来都想好好写一写spring security 系列文章,每每提笔又不知何处下笔,又赖于spring security体系强大又过于繁杂,且spring security 与auth2.0结合的 ...

  5. Spring Security系列教程11--Spring Security核心API讲解

    前言 经过前面几个章节的学习,一一哥 带大家实现了基于内存和数据库模型的认证与授权,尤其是基于自定义的数据库模型更是可以帮助我们进行灵活开发.但是前面章节的内容,属于让我们达到了 "会用&q ...

  6. Spring Security系列教程-Spring Security核心API讲解

    前言 经过前面几个章节的学习,一一哥 带大家实现了基于内存和数据库模型的认证与授权,尤其是基于自定义的数据库模型更是可以帮助我们进行灵活开发.但是前面章节的内容,属于让我们达到了 "会用&q ...

  7. Myth源码解析系列之六- 订单下单流程源码解析(发起者)

    前面一章我们走完了服务启动的源码,这次我们进入下单流程的源码解析~ 订单下单流程源码解析(发起者) 首先保证myth-demo-springcloud-order.myth-demo-springcl ...

  8. Quantopian教程源码解析及实战

    Quantopian教程源码解析及实战 ​ 本文主要侧重于采用Quantopian进行实际的金融策略分析,因此阅读本文前,需要对Quantopian的有基本的了解,建议先阅读Quantopian的tu ...

  9. 云原生小课堂|Envoy请求流程源码解析(三):请求解析

    ​ 前言 Envoy 是一款面向 Service Mesh 的高性能网络代理服务.它与应用程序并行运行,通过以平台无关的方式提供通用功能来抽象网络.当基础架构中的所有服务流量都通过 Envoy 网格时 ...

最新文章

  1. PHP使用curl_multi_add_handle并行处理
  2. 加性注意力机制、训练推理效率优于其他Transformer变体,这个Fastformer的确够快...
  3. 模板模式(C++) 【转】
  4. gdb工作原理(二)
  5. Java BIO、NIO、AIO 学习
  6. dcf是ea211发动机吗_大众有的车怎么把ea211换成dcf了?
  7. 深度对比Apache CarbonData、Hudi和Open Delta三大开源数据湖方案
  8. 国外注册的域名dns服务器换回国内dns服务器的详细教程!...
  9. FastJson(阿里巴巴)基础
  10. 计算机工作组如何添加打印机,局域网添加打印机,详细教您局域网打印机怎么添加...
  11. Jupyter/IPython笔记本集合 !(附大量资源链接)-上篇
  12. 雷林鹏分享:PHP 创建 MySQL 表
  13. Cisco switch spanning-tree priority 0
  14. 翼灵物联工作室第一次考试总结
  15. Packet Tracer - 排除 VLAN 间路由故障
  16. 海外游戏广告投放渠道
  17. 淘宝商品详情API接口(商品价格接口,商品属性接口)
  18. linux autoconf手动安装,autoconf安装不成功,求指导
  19. 微擎url模式解读_微擎常用开发文档
  20. python Word 文档

热门文章

  1. Win10下Keil5的C51和ARM共存的开发环境配置
  2. 仅剩一个月,如何备考才能通过初级会计考试?
  3. 支持向量机(一)——深入理解函数间隔与几何间隔
  4. Gingko Framework:页面参数中文乱码解决
  5. 招生通知+4,北京大学计算机学院+中国科学技术大学信息技术学院+吉林大学人工智能学院+深圳大学计算机学院
  6. Winds下如何使用CMD命令进入MySQL数据库
  7. C# ObservableCollection和List的区别总结
  8. 倍福TwinCAT(贝福Beckhoff)常见问题(FAQ)-人机界面快速入门 TC3
  9. Python学习笔记(一)1.Python中end=和sep=的用法与区别。
  10. 弹窗Modal实现和有滚动条偏移解决方法。