目录
(1)URL 权限控制:调用 HttpSecurity#authorizeRequests() 方法,开始配置 URL 的权限控制。
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http.formLogin();//表单提交
- // 授权 -> 认证拦截
- http.authorizeRequests()
- .antMatchers("/login.html", "/error.html", "/main.html").permitAll()
- .antMatchers("/admin/test").hasAnyAuthority("admin")
- .anyRequest().authenticated();// 所有请求都必须认证
- http.csrf().disable(); //关闭csrf防护
- }
(2)方法权限控制:修改 WebSecurityConfig配置类,增加 @EnableGlobalMethodSecurity 注解,开启对 Spring Security 注解的方法,进行权限验证。
- @Configuration // 标记为注解类
- @EnableGlobalMethodSecurity(prePostEnabled = true)
- public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
- }
FilterSecurityInterceptor 进行资源拦截,当拦截资源为方法时,还会进入到拦截器MethodSecurityInterceptor,它们的继承关系如下图

授权流程如下

1.拦截请求,已认证用户访问受保护的web资源将被SecurityFilterChain中的FilterSecurityInterceptor 的子类拦截。
2. 获取资源访问策略,FilterSecurityInterceptor 会从 SecurityMetadataSource 的子类DefaultFilterInvocationSecurityMetadataSource 获取要访问当前资源所需要的权限Collection。SecurityMetadataSource其实就是读取访问策略的抽象,读取的内容就是我们配置的访问规则。
3. 最后,FilterSecurityInterceptor 会调用 AccessDecisionManager 进行授权决策,若决策通过,则允许访问资源,否则将禁止访问。
进入 FilterSecurityInterceptor,进入 FilterSecurityInterceptor#doFilter 方法
- public void doFilter(ServletRequest request, ServletResponse response,
- FilterChain chain) throws IOException, ServletException {
- FilterInvocation fi = new FilterInvocation(request, response, chain);
- // 从 invoke 开始
- invoke(fi);
- }
然后进入 invoke 方法
- 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);
- }
- }
进入 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());
- }
- // 1-获取访问资源的权限->资源是authenticated,还是permitAll
- 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);
- }
- // 2-获取登陆用户的权限
- Authentication authenticated = authenticateIfRequired();
-
- // Attempt authorization
- try {
- // 3-核心投票方法:尝试进行权限验证
- 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);
- }
- }
进入权限验证决策方法,AccessDecisionManager#decide,AccessDecisionManager 有三个实现,默认使用 AffirmativeBased,其中两种决策方式,文章后边也会介绍。此处,我们先看第一个

进入 AffirmativeBased#decide 方法,我们去看它的决策实现
- public void decide(Authentication authentication, Object object,
- Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
- int deny = 0;
- // 对权限进行循环遍历
- for (AccessDecisionVoter voter : getDecisionVoters()) {
- // 获取投票结果
- int result = voter.vote(authentication, object, configAttributes);
- if (logger.isDebugEnabled()) {
- logger.debug("Voter: " + voter + ", returned: " + result);
- }
- switch (result) {
- case AccessDecisionVoter.ACCESS_GRANTED://同意返回 -> 跳出循环
- return;
- case AccessDecisionVoter.ACCESS_DENIED: //拒绝
- deny++; // 统计拒绝次数
- break; // 继续下一次循环
- default:
- break; // 弃权 -> 继续下一次循环
- }
- }
- // 如果统计拒绝次数 > 0
- if (deny > 0) {
- throw new AccessDeniedException(messages.getMessage(
- "AbstractAccessDecisionManager.accessDenied", "Access is denied"));
- }
- // 如果每个投票者都选择放弃,默认也会抛出 AccessDeniedException
- // private boolean allowIfAllAbstainDecisions = false; 是否允许放弃决策,默认flase
- checkAllowIfAllAbstainDecisions();
- }
至此,投票决策逻辑分析完成。
AccessDecisionManager 采用投票的方式来确定是否能够访问受保护资源。 AccessDecisionManager 中包含的一系列 AccessDecisionVoter 将会被用来对Authentication是否有权访问受保护对象进行投票,AccessDecisionManager根据投票结果,做出最终决策。
- // 做出最终的访问控制(授权)决策。
- public interface AccessDecisionManager {
- // ~ Methods
- // 为传递的参数解析访问控制决策。
- // authentication 调用方法的调用者(非空)
- // object 对象被调用的受保护对象
- // configAttributes 与被调用的受保护对象关联的配置属性
- void decide(Authentication authentication, Object object,
- Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
- InsufficientAuthenticationException;
-
- // 指示此AccessDecisionManager是否能够处理传入的ConfigAttribute。
- boolean supports(ConfigAttribute attribute);
-
- // 指示AccessDecisionManager实现是否能够为指定的受保护对象类型提供访问控制决策。
- boolean supports(Class<?> clazz);
- }
AffirmativeBased
如果有 AccessDecisionVoter 的投票为 ACCESS_GRANTED 则同意用户进行访问,但如果统计到反对票,则将抛出AccessDeniedException。
- public void decide(Authentication authentication, Object object,
- Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
- int deny = 0;
- // 获取投票结果
- for (AccessDecisionVoter voter : getDecisionVoters()) {
- int result = voter.vote(authentication, object, configAttributes);
- if (logger.isDebugEnabled()) {
- logger.debug("Voter: " + voter + ", returned: " + result);
- }
- switch (result) {
- case AccessDecisionVoter.ACCESS_GRANTED: // 匹配到同意票,直接返回
- return;
- case AccessDecisionVoter.ACCESS_DENIED: // 决绝
- deny++;
- break;
- default:
- break;
- }
- }
- // 统计到拒绝票,抛出异常
- if (deny > 0) {
- throw new AccessDeniedException(messages.getMessage(
- "AbstractAccessDecisionManager.accessDenied", "Access is denied"));
- }
- // 全体弃票,抛出异常
- checkAllowIfAllAbstainDecisions();
- }
Spring security默认使用的是AffirmativeBased。
ConsensusBased(少数服从多数)
如果赞成票多于反对票则表示通过,如果赞成票与反对票相同且不等于0,并且属性allowIfEqualGrantedDeniedDecisions 的值为true,则表示通过,否则将抛出异常AccessDeniedException。参数allowIfEqualGrantedDeniedDecisions的值默认为true。
- public void decide(Authentication authentication, Object object,
- Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
- int grant = 0;
- int deny = 0;
- for (AccessDecisionVoter voter : getDecisionVoters()) {
- // 1-获取投票结果
- int result = voter.vote(authentication, object, configAttributes);
- if (logger.isDebugEnabled()) {
- logger.debug("Voter: " + voter + ", returned: " + result);
- }
- switch (result) {
- case AccessDecisionVoter.ACCESS_GRANTED:
- grant++; // 统计同意次数
- break;
- case AccessDecisionVoter.ACCESS_DENIED:
- deny++; // 统计决绝次数
- break;
- default:
- break;
- }
- }
- if (grant > deny) { // 2-比较同意次数和拒绝次数
- return;
- }
- if (deny > grant) {
- throw new AccessDeniedException(messages.getMessage(
- "AbstractAccessDecisionManager.accessDenied", "Access is denied"));
- }
- if ((grant == deny) && (grant != 0)) { // 3-判断是否允许两个次数相等的情况
- if (this.allowIfEqualGrantedDeniedDecisions) {
- return;
- }
- else {
- throw new AccessDeniedException(messages.getMessage(
- "AbstractAccessDecisionManager.accessDenied", "Access is denied"));
- }
- }
- // 4-对弃权票的处理
- checkAllowIfAllAbstainDecisions();
- }
UnanimousBased(一票否决)
如果没有反对票,但是有赞成票,则表示通过。
- public void decide(Authentication authentication, Object object,
- Collection<ConfigAttribute> attributes) throws AccessDeniedException {
- int grant = 0;
- List<ConfigAttribute> singleAttributeList = new ArrayList<>(1);
- singleAttributeList.add(null);
- for (ConfigAttribute attribute : attributes) {
- singleAttributeList.set(0, attribute);
- for (AccessDecisionVoter voter : getDecisionVoters()) {
- // 获取投票结果
- int result = voter.vote(authentication, object, singleAttributeList);
- if (logger.isDebugEnabled()) {
- logger.debug("Voter: " + voter + ", returned: " + result);
- }
- switch (result) {
- case AccessDecisionVoter.ACCESS_GRANTED:
- grant++;
- break;
- case AccessDecisionVoter.ACCESS_DENIED: // 有拒绝,直接抛异常
- throw new AccessDeniedException(messages.getMessage(
- "AbstractAccessDecisionManager.accessDenied",
- "Access is denied"));
- default:
- break;
- }
- }
- }
- // To get this far, there were no deny votes
- if (grant > 0) { // 没有拒绝票
- return;
- }
- // To get this far, every AccessDecisionVoter abstained
- checkAllowIfAllAbstainDecisions();
- }
至此,AccessDecisionManager 的三种实现分析完毕。