【Spring Security OAuth2笔记系列】- Security控制授权- Spring Security源码解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/mr_zhuqiang/article/details/82259731

Spring Security源码解析

spring security的基本原理之前讲解过了。这章主要看后面两个:

  • FilterSecurityInterceptor

    决定该用户是否有权限访问指定的资源

  • ExceptionTranslationFilter

    异常处理,如果不能访问,则处理FilterSecurityInterceptor中抛出的异常

AnonymousAuthenticationFilter

AnonymousAuthenticationFilter : 匿名过滤器,位置固定,前面所有的都走完之后,会经过该过滤器

该过滤器之前自己也是备受折磨,特别是在调试oath2的时候,在没有正确开始basic登录的时候,
就一直在抱怨,为什么会走这个呢?

看看最主要的源码:

public AnonymousAuthenticationFilter(String key) {
  // 固定了用户信息和角色
  this(key, "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
}

public AnonymousAuthenticationFilter(String key, Object principal,
    List<GrantedAuthority> authorities) {
  Assert.hasLength(key, "key cannot be null or empty");
  Assert.notNull(principal, "Anonymous authentication principal must be set");
  Assert.notNull(authorities, "Anonymous authorities must be set");
  this.key = key;
  this.principal = principal;  // 用户信息不是一个对象,是一个字符串
  this.authorities = authorities;
}

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

  // 如果之前没有身份认证
  // 则创建一个 AnonymousAuthenticationToken
  // 也就是说到达 FilterSecurityInterceptor 中的时候一定有一个身份信息
  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);
}

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

  return auth;
}

FilterSecurityInterceptor

看源码技巧:找准一个关键入口,然后分析调用关系和类图

  • FilterSecurityInterceptor
  • AccessDecisionManager 管理什么呢?AccessDecisionVoter :投票者

    就是用来一系列的判定,是否可以进行访问什么的

    • AffirmativeBased 只要有一票否决则不能访问 (默认使用)

      在未登录的时候用的是只要有一个拒绝则不能访问,在登录后,只要有一个允许则放行。。有点懵逼

    • ConsensusBased 通过/不通过 哪一个票数多就遵循哪一个
    • UnanimousBased 只要有一票否决则不允许访问
  • AccessDecisionVoter 投票者 spring3-,由一堆实现类来解决
    • WebExpressionVoter spring3+ 由该类全部包办,它投过就过,不过就不过
  • ——– 以上是三个核心的类———
  • SecurityConfig 所有的配置信息
  • ConfigAttribute 对应了每一个url的配置信息
  • SecurityContextHolder
  • Authentication 用户身份信息,用户拥有的权限信息

主流程:拿到 配置信息,用户身份信息,请求信息 然后给投票者进行投票;最后根据策略进行决定是否放行

从两个流程进行分析:

  1. 未登录访问被拦截
  2. 登录后访问

未登录访问被拦截

访问: http://localhost:8080/user/1

public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
        Filter {

  public void doFilter(ServletRequest request, ServletResponse response,
    FilterChain chain) throws IOException, ServletException {
    // 拿到请求和响应封装成一个 invocation(调用)
    FilterInvocation fi = new FilterInvocation(request, response, chain);
    invoke(fi);
  }
  public void invoke(FilterInvocation fi) throws IOException, ServletException {
    // 保证每次请求只被检查一次,依据就是 request中的 FILTER_APPLIED
        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);
        }
    }
}

// 抽象父类
public abstract class AbstractSecurityInterceptor implements InitializingBean,
        ApplicationEventPublisherAware, MessageSourceAware {
      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());
            }

        // 获取配置信息中对访问的url匹配的权限
        // 这里会返回 hasRole('ROLE_ADMIN') ,也就是之前配置的admin角色
            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 {
          //  accessDecisionManager 的实现是 AffirmativeBased,里面只有一个 WebExpressionVoter
          // 如果没有通过就会抛出异常
                this.accessDecisionManager.decide(authenticated, object, attributes);
            }
            catch (AccessDeniedException accessDeniedException) {
                publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
                        accessDeniedException));
          // 在这里异常会被 org.springframework.security.web.access.ExceptionTranslationFilter#doFilter 捕获到
                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);
            }
        }    
}


// 默认投票策略
public class AffirmativeBased extends AbstractAccessDecisionManager {

    public AffirmativeBased(List<AccessDecisionVoter<? extends Object>> decisionVoters) {
        super(decisionVoters);
    }

    public void decide(Authentication authentication, Object object,
            Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
        int deny = 0;

        for (AccessDecisionVoter voter : getDecisionVoters()) {
      // WebExpressionVoter 去投票
            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"));
        }

        // To get this far, every AccessDecisionVoter abstained
        checkAllowIfAllAbstainDecisions();
    }
}


public class ExceptionTranslationFilter extends GenericFilterBean {

    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) {
          // 检测响应是否已经提交
          // 提交过得响应已经写了 http状态码和响应头
                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);
            }
        }
    }

    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);
          // 如果是 Anonymous 身份信息,又是拒绝访问异常
          // 则发送,并开始授权
                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);
            }
        }
    }

    protected void sendStartAuthentication(HttpServletRequest request,
      HttpServletResponse response, FilterChain chain,
      AuthenticationException reason) throws ServletException, IOException {
      SecurityContextHolder.getContext().setAuthentication(null);
      requestCache.saveRequest(request, response);
      logger.debug("Calling Authentication entry point.");
      // org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint#commence
      // 该类是我们之前配置的  loginFormUrl = /authentication/require 的处理器端点
      // 该端点会获取到 之前配置的地址然后使用
      // redirectStrategy.sendRedirect(request, response, redirectUrl); 进行跳转
      // 该访问之后就完成了拒绝并引导授权的功能
      authenticationEntryPoint.commence(request, response, reason);
  }
}

登录后访问

该源码就不记录了。篇幅太多了;

猜你喜欢

转载自blog.csdn.net/mr_zhuqiang/article/details/82259731