美文网首页
Spring Security源码(一):认证、授权、过滤器链

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

作者: guideEmotion | 来源:发表于2019-07-27 18:26 被阅读0次

    因为看了很多博客,发现很多人的使用风格都不一样,有点懵。感觉最好的方法就是多看一些源码,然后自己选择想要的使用方式。
    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;
            }
    
            ...
        }
    
    
    • authenticated: 当前认证的Authentication
    • object: 请求的路径
    • attributes:使用当前的访问资源路径去匹配我们自己定义的匹配规则

    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授权过程

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/ytthrctx.html