Spring Security源码(一):认证、授权、过滤器链

2019-07-27  本文已影响0人  guideEmotion

因为看了很多博客,发现很多人的使用风格都不一样,有点懵。感觉最好的方法就是多看一些源码,然后自己选择想要的使用方式。
spring security版本:2.1.6.RELEASE

一 认证三元素

这里先讲这三种的关系
AuthenticationManager、ProviderManager、AuthenticationProvider

Authentication

封装了用户身份信息

public interface Authentication extends Principal, Serializable {
    //#1.权限结合,可使用AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_ADMIN")返回字符串权限集合
    Collection<? extends GrantedAuthority> getAuthorities();
    //#2.用户名密码认证时可以理解为密码
    Object getCredentials();
    //#3.认证时包含的一些信息。
    Object getDetails();
    //\#4.用户名密码认证时可理解时用户名
    Object getPrincipal();
    #5.是否被认证,认证为true    
    boolean isAuthenticated();
    #6.设置是否能被认证
    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;

AuthenticationManager

一个接口,只有一个方法

public interface AuthenticationManager {

    Authentication authenticate(Authentication authentication)
            throws AuthenticationException;
}

ProviderManager

ProviderManager是AuthenticationManager的实现类,提供了基本认证实现逻辑和流程;
先看这个方法这个方法,是用来认证的方法

    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();//获取authentication的类型
        AuthenticationException lastException = null;
        Authentication result = null;//默认null
        boolean debug = logger.isDebugEnabled();

        //一个ProviderManager有一个AuthenticationProvider的集合列表
        for (AuthenticationProvider provider : getProviders()) {
            //当前AuthenticationProvider是否支持认证这种类型的authentication
            if (!provider.supports(toTest)) {
                continue;
            }

            if (debug) {
                logger.debug("Authentication attempt using "
                        + provider.getClass().getName());
            }

            try {
                result = provider.authenticate(authentication);//真正的认证是AuthenticationProvider完成的

                if (result != null) {//不等于null,说明认证成功了
                    copyDetails(authentication, result);//将authentication的detail信息设置到result上
                    break;
                }
            }
            catch (AccountStatusException e) {//认证过程中如果出现了异常便捕获
                prepareException(e, authentication);
                // SEC-546: Avoid polling additional providers if auth failure is due to
                // invalid account status
                throw e;//这种异常的处理方式是抛出,所以这种异常出现了。不会继续用其他provider继续认证。就是认证失败了
            }
            catch (InternalAuthenticationServiceException e) {
                prepareException(e, authentication);
                throw e;
            }
            catch (AuthenticationException e) {
                lastException = e;//这种异常没有抛出,捕获住了。并继续for循环,尝试交给其他的provider尝试认证。
            }
        }
                //如果所有的认证都不通过,并且当前ProviderManager存在父AuthenticationManager
        if (result == null && parent != null) {
            // Allow the parent to try.
            try {
                result = parentResult = parent.authenticate(authentication);//注意这里赋了两次值
            }
            catch (ProviderNotFoundException e) {
                // ignore as we will throw below if no other exception occurred prior to
                // calling parent and the parent
                // may throw ProviderNotFound even though a provider in the child already
                // handled the request
            }
            catch (AuthenticationException e) {
                lastException = parentException = e;
            }
        }

        if (result != null) {
            if (eraseCredentialsAfterAuthentication
                    && (result instanceof CredentialsContainer)) {
                // Authentication is complete. Remove credentials and other secret data
                // from authentication  认证完成,清除敏感信息
                ((CredentialsContainer) result).eraseCredentials();
            }

            // If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
            // This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
            if (parentResult == null) {
                eventPublisher.publishAuthenticationSuccess(result);//放着重复发布成功事件
            }
            return result;
        }

        // Parent was null, or didn't authenticate (or throw an exception).

        if (lastException == null) {
            lastException = new ProviderNotFoundException(messages.getMessage(
                    "ProviderManager.providerNotFound",
                    new Object[] { toTest.getName() },
                    "No AuthenticationProvider found for {0}"));
        }

        // If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
        // This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it  防止重复
        if (parentException == null) {
            prepareException(lastException, authentication);
        }

        throw lastException;//抛出异常
    }

关键点

  1. lastException:在认证过程中,可能会由多个组件来尝试认证并认证失败。但只记录最后一次认证失败得异常
  2. 因为可能会有父AuthenticationManager参与认证,所以这里面做了一些处理。防止重复发布成功/失败得事件

AuthenticationProvider

AuthenticationProvider本身也就是一个接口,它有实现类AbstractUserDetailsAuthenticationProvider和AbstractUserDetailsAuthenticationProvider的子类DaoAuthenticationProvider

只有两个方法

    Authentication authenticate(Authentication authentication)
            throws AuthenticationException;

    boolean supports(Class<?> authentication);//检查是否支持认证指定类型的Authentication

AbstractUserDetailsAuthenticationProvider

    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
                () -> messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.onlySupports",
                        "Only UsernamePasswordAuthenticationToken is supported"));

        // Determine username
        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
                : authentication.getName();

        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);//先根据用户名从缓存中找

        if (user == null) {
            cacheWasUsed = false;

            try {
                user = retrieveUser(username,
                        (UsernamePasswordAuthenticationToken) authentication);//找到正确得用户身份,抽象方法待实现
            }
            catch (UsernameNotFoundException notFound) {
                logger.debug("User '" + username + "' not found");
                                //不管怎么样,只要查找用户失败都会抛出异常。只是有隐藏不隐藏用户没找到的信息区别
                if (hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.badCredentials",
                            "Bad credentials"));
                }
                else {
                    throw notFound;
                }
            }

            Assert.notNull(user,
                    "retrieveUser returned null - a violation of the interface contract");//null判断
        }

        try {
            preAuthenticationChecks.check(user);//预先检查返回的身份信息,比如账号锁定等等属性
            additionalAuthenticationChecks(user,
                    (UsernamePasswordAuthenticationToken) authentication);//检查用户的登陆信息
        }
        catch (AuthenticationException exception) {
            if (cacheWasUsed) {
                // There was a problem, so try again after checking
                // we're using latest data (i.e. not from the cache)
                cacheWasUsed = false;//如果缓存的身份比对失败,则查一次最新的再进行比对
                user = retrieveUser(username,
                        (UsernamePasswordAuthenticationToken) authentication);
                preAuthenticationChecks.check(user);
                additionalAuthenticationChecks(user,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
            else {
                throw exception;
            }
        }

        postAuthenticationChecks.check(user);//后置检查,执行到这步说明上路认证成功了,没有抛出异常,导致中止

        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);//加入缓存
        }

        Object principalToReturn = user;

        if (forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }

        return createSuccessAuthentication(principalToReturn, authentication, user);//创建一个新的身份信息,并返回
    }

总结
这是一个抽象类,是模板模式。定义好了认证流程,我们去实现流程中的一些环节方法。达到了自定义认证的效果

DaoAuthenticationProvider

AbstractUserDetailsAuthenticationProvider的子类,实现了一些抽象方法

retrieveUser

    protected final UserDetails retrieveUser(String username,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        prepareTimingAttackProtection();
        try {
            //通过UserDetailsService去查找用户
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        }
        catch (UsernameNotFoundException ex) {
            mitigateAgainstTimingAttack(authentication);
            throw ex;
        }
        catch (InternalAuthenticationServiceException ex) {
            throw ex;
        }
        catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }

主要就是用UserDetailsService去查找用户


additionalAuthenticationChecks

    @SuppressWarnings("deprecation")
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        if (authentication.getCredentials() == null) {//用户的认证信息没有密码
            logger.debug("Authentication failed: no credentials provided");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }

        String presentedPassword = authentication.getCredentials().toString();//提交的密码

        if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {//加密对象会自动加密然后比较
            logger.debug("Authentication failed: password does not match stored value");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }
    }

createSuccessAuthentication

    protected Authentication createSuccessAuthentication(Object principal,
            Authentication authentication, UserDetails user) {
        boolean upgradeEncoding = this.userDetailsPasswordService != null
                && this.passwordEncoder.upgradeEncoding(user.getPassword());
        if (upgradeEncoding) {
            String presentedPassword = authentication.getCredentials().toString();
            String newPassword = this.passwordEncoder.encode(presentedPassword);
            user = this.userDetailsPasswordService.updatePassword(user, newPassword);
        }
        return super.createSuccessAuthentication(principal, authentication, user);
    }

本质还是调用了父类的方法,就是新建了一个Authentication,然后将UserDetails信息传递过去

UserDetailsService

UserDetailsService是一个接口,提供了一个方法

public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

UserDetails

public interface UserDetails extends Serializable {
    #1.权限集合
    Collection<? extends GrantedAuthority> getAuthorities();
    #2.密码   
    String getPassword();
    #3.用户民
    String getUsername();
    #4.用户是否过期
    boolean isAccountNonExpired();
    #5.是否锁定 
    boolean isAccountNonLocked();
    #6.用户密码是否过期 
    boolean isCredentialsNonExpired();
    #7.账号是否可用(可理解为是否删除)
    boolean isEnabled();
}

UserDetailsManager

public interface UserDetailsManager extends UserDetailsService {

    /**
     * Create a new user with the supplied details.
     */
    void createUser(UserDetails user);

    /**
     * Update the specified user.
     */
    void updateUser(UserDetails user);

    /**
     * Remove the user with the given login name from the system.
     */
    void deleteUser(String username);

    /**
     * Modify the current user's password. This should change the user's password in the
     * persistent user repository (datbase, LDAP etc).
     *
     * @param oldPassword current password (for re-authentication if required)
     * @param newPassword the password to change to
     */
    void changePassword(String oldPassword, String newPassword);

    /**
     * Check if a user with the supplied login name exists in the system.
     */
    boolean userExists(String username);

}

感觉这个接口没什么用,就是用来管理用户类的

二 过滤器原理

SS在http后台中起作用主要是基于Servlet Filters的,我们先来看看什么是 Filter 是如何作用在 Servlet 中的。

image.png
可以看到不同的过滤器作用在 Servlet 之前,多个形成的就是一条过滤器链( Filters Chain ),每个Filter 有个Order顺序,可以通过 @Order 来设置Filter 的 Order ,设置前后顺序。SS本身也是一个 Filter,使用一个代理,委托了一个 Filter Chain,如下图 :
image.png

springSecurityFilterChain 是个接口DefaultSecurityFilterChain是它的实现类,而DefaultSecurityFilterChain 内部存在这一个 Filters 列表,关于SS中的过滤器和他们的执行顺序(Order)可以查看 官方文档,当我们需要自定义Filter的时候就会用到。 当请求到来时,在 ss 里边的 Filter就会作用请求,如下图 :

image.png

三 授权过程

spring security的过滤器链

在上面我们说到了SS有自己的一条过滤器链,下面就是截图:(执行顺序就是集合中的顺序)

image.png

下面说一下几个比较重要的 Filter 的处理逻辑

UsernamePasswordAuthenticationFilter

整个调用流程是,先调用其父类 AbstractAuthenticationProcessingFilter.doFilter() 方法,然后再执行 UsernamePasswordAuthenticationFilter.attemptAuthentication() 方法进行验证;

AbstractAuthenticationProcessingFilter

父类是AbstractAuthenticationProcessingFilter(又是模板模式,子类实现抽象方法,父类定好流程)

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
      #1.判断当前的filter是否可以处理当前请求,不可以的话则交给下一个filter处理
        if (!this.requiresAuthentication(request, response)) {
            chain.doFilter(request, response);
        } else {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Request is to process authentication");
            }

            Authentication authResult;
            try {
            #2.抽象方法由子类来实现
                authResult = this.attemptAuthentication(request, response);
                if (authResult == null) {
                    return;
                }
                #2.认证成功后,处理一些与session相关的方法 
                this.sessionStrategy.onAuthentication(authResult, request, response);
            } catch (InternalAuthenticationServiceException var8) {
                this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
               #3.认证失败后的的一些操作  
               this.unsuccessfulAuthentication(request, response, var8);
                return;
            } catch (AuthenticationException var9) {
                this.unsuccessfulAuthentication(request, response, var9);
                return;
            }

            if (this.continueChainBeforeSuccessfulAuthentication) {
                chain.doFilter(request, response);
            }
            #3. 认证成功后的相关回调方法 主要将当前的认证放到SecurityContextHolder中
            this.successfulAuthentication(request, response, chain, authResult);
        }
    }

      public abstract Authentication attemptAuthentication(HttpServletRequest var1, HttpServletResponse var2) throws AuthenticationException, IOException, ServletException;

requiresAuthentication
判断是否需要当前filter处理,就是判断路径、请求方法等等

protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
        return this.requiresAuthenticationRequestMatcher.matches(request);
    }

以UsernamePasswordAuthenticationFilter的构造方法为例

    public UsernamePasswordAuthenticationFilter() {
        super(new AntPathRequestMatcher("/login", "POST"));
    }

successfulAuthentication

protected void successfulAuthentication(HttpServletRequest request,
            HttpServletResponse response, FilterChain chain, Authentication authResult)
            throws IOException, ServletException {

        if (logger.isDebugEnabled()) {
            logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
                    + authResult);
        }

        SecurityContextHolder.getContext().setAuthentication(authResult);

        rememberMeServices.loginSuccess(request, response, authResult);

        // Fire event
        if (this.eventPublisher != null) {
            eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                    authResult, this.getClass()));
        }

        successHandler.onAuthenticationSuccess(request, response, authResult);
    }

  1. 将当前认证成功的 Authentication 放置到 SecurityContextHolder中;
  2. 记住我功能
  3. 调用其它可扩展的 handlers继续处理该认证成功以后的回调事件;(实现AuthenticationSuccessHandler接口即可)

UsernamePasswordAuthenticationFilter

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        #1.判断请求的方法必须为POST请求
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            #2.从request中获取username和password
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }

            username = username.trim();
            #3.构建UsernamePasswordAuthenticationToken(两个参数的构造方法setAuthenticated(false))
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
             #4. 调用 AuthenticationManager 进行验证(子类ProviderManager遍历所有的AuthenticationProvider认证)
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

这里的认证就是第一节的内容(this.getAuthenticationManager().authenticate(authRequest))

AnonymousAuthenticationFilter

从上图中过滤器的执行顺序图中可以看出AnonymousAuthenticationFilter过滤器是在UsernamePasswordAuthenticationFilter等过滤器之后,如果它前面的过滤器都没有认证成功,Spring Security则为当前的SecurityContextHolder中添加一个Authenticaiton 的匿名实现类AnonymousAuthenticationToken;

        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        #1. 如果前面的过滤器都没认证通过,则SecurityContextHolder中Authentication为空
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            #2.为当前的SecurityContextHolder中添加一个匿名的AnonymousAuthenticationToken
            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);
    }

    #3.创建匿名的AnonymousAuthenticationToken
    protected Authentication createAuthentication(HttpServletRequest request) {
        AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,
                principal, authorities);
        auth.setDetails(authenticationDetailsSource.buildDetails(request));

        return auth;
    }
    
        /**
     * Creates a filter with a principal named "anonymousUser" and the single authority
     * "ROLE_ANONYMOUS".
     *
     * @param key the key to identify tokens created by this filter
     */
     ##.创建一个用户名为anonymousUser 授权为ROLE_ANONYMOUS
    public AnonymousAuthenticationFilter(String key) {
        this(key, "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
    }

总结

  1. 判断SecurityContextHolder中Authentication为否为空;
  2. 如果空则为当前的SecurityContextHolder中添加一个匿名的AnonymousAuthenticationToken(用户名为 anonymousUser 的AnonymousAuthenticationToken)

ExceptionTranslationFilter

ExceptionTranslationFilter 异常处理过滤器,该过滤器用来处理在系统证授权过程中抛出的异常(也就是下一个过滤器FilterSecurityInterceptor),主要是 处理 AuthenticationException 和 AccessDeniedException

    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);
            this.logger.debug("Chain processed normally");
       #捕获的是下一个过滤器中抛出的异常,前面抛出的异常不归它管
        } catch (IOException var9) {
            throw var9;
        } catch (Exception var10) {
            #.判断是不是AuthenticationException
            Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var10);
            RuntimeException ase = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
            if (ase == null) {
                #.  判断是不是AccessDeniedException
                ase = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
            }

            if (ase == null) {
                if (var10 instanceof ServletException) {
                    throw (ServletException)var10;
                }

                if (var10 instanceof RuntimeException) {
                    throw (RuntimeException)var10;
                }

                throw new RuntimeException(var10);
            }

            if (response.isCommitted()) {
                throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", var10);
            }

            this.handleSpringSecurityException(request, response, chain, (RuntimeException)ase);
        }

    }

FilterSecurityInterceptor

此过滤器为认证授权过滤器链中最后一个过滤器,该过滤器之后就是请求真正的/xx服务

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        this.invoke(fi);
    }

    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        if (fi.getRequest() != null && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null && this.observeOncePerRequest) {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } else {
            if (fi.getRequest() != null && this.observeOncePerRequest) {
                fi.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
            }
            #1. before invocation重要
            InterceptorStatusToken token = super.beforeInvocation(fi);

            try {
                #2. 可以理解开始请求真正的 /xx服务
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            } finally {
                super.finallyInvocation(token);
            }
            #3. after Invocation
            super.afterInvocation(token, (Object)null);
        }

    }
  1. before invocation重要(调用 AccessDecisionManager 来验证当前已认证成功的用户是否有权限访问该资源)
    2 .请求真正的/xx服务
  2. after Invocation

before invocation: AccessDecisionManager

protected InterceptorStatusToken beforeInvocation(Object object) {
        ...

        Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
                .getAttributes(object);

        ...
        Authentication authenticated = authenticateIfRequired();

        // Attempt authorization
        try {
            #1.重点
            this.accessDecisionManager.decide(authenticated, object, attributes);
        }
        catch (AccessDeniedException accessDeniedException) {
            publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,accessDeniedException));

            throw accessDeniedException;
        }

        ...
    }

AccessDecisionManager 是如何授权的

Spring Security默认使用AffirmativeBased实现AccessDecisionManager的 decide 方法来实现授权

public void decide(Authentication authentication, Object object,
            Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
        int deny = 0;
        #1.调用AccessDecisionVoter 进行vote(投票)
        for (AccessDecisionVoter voter : getDecisionVoters()) {
            int result = voter.vote(authentication, object, configAttributes);

            if (logger.isDebugEnabled()) {
                logger.debug("Voter: " + voter + ", returned: " + result);
            }

            switch (result) {
            #1.1只要有voter投票为ACCESS_GRANTED,则通过 直接返回
            case AccessDecisionVoter.ACCESS_GRANTED://1
                return;
            @#1.2只要有voter投票为ACCESS_DENIED,则记录一下
            case AccessDecisionVoter.ACCESS_DENIED://-1
                deny++;

                break;

            default:
                break;
            }
        }

        if (deny > 0) {
        #2.如果有一个及以上AccessDecisionVoter(姑且称之为投票者吧)都投ACCESS_DENIED,则直接就不通过了
            throw new AccessDeniedException(messages.getMessage(
                    "AbstractAccessDecisionManager.accessDenied", "Access is denied"));
        }

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

  1. 调用AccessDecisionVoter进行vote(投票)
  2. 只要有投通过(ACCESS_GRANTED)票,则直接判为通过。
  3. 如果没有投通过则deny++,最后判断if(deny>0 抛出AccessDeniedException(未授权)

AccessDecisionVoter的实现类WebExpressionVoter

    public int vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes) {
        assert authentication != null;

        assert fi != null;

        assert attributes != null;

        WebExpressionConfigAttribute weca = this.findConfigAttribute(attributes);
        if (weca == null) {
            return 0;
        } else {
            EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, fi);
            ctx = weca.postProcess(ctx, fi);
            return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? 1 : -1;
        }
    }

到此位置authentication当前用户信息,fl当前访问的资源路径及attributes当前资源路径的决策(即是否需要认证)。剩下就是判断当前用户的角色Authentication.authorites是否权限访问决策访问当前资源fi

更多过滤器介绍

Spring Security 核心过滤器链分析

参考

  1. Spring Security源码分析一:Spring Security认证过程
  2. Springboot --- Spring Security (一)
  3. Spring Security源码分析二:Spring Security授权过程
上一篇下一篇

猜你喜欢

热点阅读