spring-security3(二)源码分析

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

利用断点走了一遍spring-security源码的核心部分,下面根据自己的理解对源码做了一些解释,过滤器开头的标号是运行时默认配置调用的顺序,理解了原理,我们可以通过继承和实现接口的方式扩展过滤器,权限验证器,数据查询器,投票器等等……

1.SecurityContextPersistenceFilter 从HttpSession中获取SecurityContext上下文

2.logoutFilter 如果访问地址为/j_spring_security_logout,LogoutFilter将注销用户

3.AbstractAuthenticationProcessingFilter 权限管理器如果访问地址为/j_spring_security_check则选择对应的数据查询器来获取存储的用户相关信息

4.BasicAuthenticationFilter

5.RequestCacheAwareFilter

6.SecurityContextHolderAwareRequestFilter

7.RememberMeAuthenticationFilter 如果当前SecurityContextHolder中没有用户对象,则通过cookie查找

8.AnonymousAuthenticationFilter 如果当前SecurityContextHolder中没有用户对象,则创建匿名对象

9.SessionManagementFilter 检查session是否超时

10.ExceptionTranslationFilter 调用FilterSecurityInterceptor,AbstractSecurityInterceptor使用投票器进行权限判断

11.SwitchUserFilter 用户切换高权限用户向低权限用户切换

    //从HttpSession中获取SecurityContext上下文 
    public class SecurityContextPersistenceFilter extends GenericFilterBean {

        private SecurityContextRepository repo = new HttpSessionSecurityContextRepository();

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

            //代码略.....................

            HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
            SecurityContext contextBeforeChainExecution = repo.loadContext(holder);//获得security上下文

            try {
                SecurityContextHolder.setContext(contextBeforeChainExecution);

                chain.doFilter(holder.getRequest(), holder.getResponse());//调用下一个过滤器

            } finally {
                SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
                // Crucial removal of SecurityContextHolder contents - do this before anything else.
                SecurityContextHolder.clearContext();
                repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
                request.removeAttribute(FILTER_APPLIED);
            }
        }

        //代码略.....................
    }

    //如果访问地址为/j_spring_security_logout,LogoutFilter将注销用户
    public class LogoutFilter extends GenericFilterBean {

        private String filterProcessesUrl = "/j_spring_security_logout"; 
        //代码略.....................

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

            //判断如果访问地址为/j_spring_security_logout则执行注销,否则跳过
            if (requiresLogout(request, response)) { 
                Authentication auth = SecurityContextHolder.getContext().getAuthentication();

                for (LogoutHandler handler : handlers) {
                    handler.logout(request, response, auth);
                }

                logoutSuccessHandler.onLogoutSuccess(request, response, auth);

                return;
            }

            chain.doFilter(request, response);
        }

        //代码略.....................
    }

    //权限管理器如果访问地址为/j_spring_security_check则选择对应的数据查询器来获取存储的用户相关信息
    public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements
        ApplicationEventPublisherAware, MessageSourceAware {

        //代码略.....................

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

            //判断如果访问地址为/j_spring_security_check则跳过进行权限获取和判断,否则执行AnonymousAuthenticationFilter
            if (!requiresAuthentication(request, response)) { 
                chain.doFilter(request, response); //调用下一个过滤器BasicAuthenticationFilter

                return;
            }

            Authentication authResult;

            try {
                //执行UsernamePasswordAuthenticationFilter类中的方法
                authResult = attemptAuthentication(request, response);
                if (authResult == null) {
                    // return immediately as subclass has indicated that it hasn't completed authentication
                    return;
                }
                sessionStrategy.onAuthentication(authResult, request, response);
            }
            catch (AuthenticationException failed) {
                // Authentication failed
                unsuccessfulAuthentication(request, response, failed);

                return;
            }

            // Authentication success
            if (continueChainBeforeSuccessfulAuthentication) {
                chain.doFilter(request, response);
            }

            successfulAuthentication(request, response, authResult);
        }

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

            SecurityContextHolder.getContext().setAuthentication(authResult);

            rememberMeServices.loginSuccess(request, response, authResult); //是否存入cookie

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

            successHandler.onAuthenticationSuccess(request, response, authResult);//跳转到目标页面
        }

        //代码略.....................
    }

    public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

        //代码略.....................

        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
            if (postOnly && !request.getMethod().equals("POST")) {
                throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
            }
            //获取页面提交的用户名、密码
            String username = obtainUsername(request);
            String password = obtainPassword(request);

            username = username.trim();

            //封装成token对象
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

            //代码略.....................

            //调用AbstractAuthenticationManager类中的方法
            return this.getAuthenticationManager().authenticate(authRequest);
        }

        //代码略.....................
    }

    public abstract class AbstractAuthenticationManager implements AuthenticationManager {

        //代码略.....................

        public final Authentication authenticate(Authentication authRequest) throws AuthenticationException {
            try {
                return doAuthentication(authRequest);//调用ProviderManager类中的方法
            } catch (AuthenticationException e) {
                e.setAuthentication(authRequest);

                if (clearExtraInformation) {
                    e.clearExtraInformation();
                }

                throw e;
            }
        }

        //代码略.....................
    }

    //权限认证管理器
    public class ProviderManager extends AbstractAuthenticationManager implements MessageSourceAware, InitializingBean {
        private List providers = Collections.emptyList();
        private AuthenticationManager parent;

        //代码略.....................

        public Authentication doAuthentication(Authentication authentication) throws AuthenticationException {
            Class extends Authentication> toTest = authentication.getClass();
            AuthenticationException lastException = null;
            Authentication result = null;

            for (AuthenticationProvider provider : getProviders()) {
                if (!provider.supports(toTest)) {
                    continue;
                }

                try {
                    //调用AbstractUserDetailsAuthenticationProvider类中的方法
                    result = provider.authenticate(authentication);

                    if (result != null) {
                        copyDetails(authentication, result);
                        break;
                    }
                } catch (AccountStatusException e) {
                    // SEC-546: Avoid polling additional providers if auth failure is due to invalid account status
                    eventPublisher.publishAuthenticationFailure(e, authentication);
                    throw e;
                } catch (AuthenticationException e) {
                    lastException = e;
                }
            }

            if (result == null && parent != null) {
                // Allow the parent to try.
                try {
                    result = 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 = e;
                }
            }

            if (result != null) {
                if (eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
                    // Authentication is complete. Remove credentials and other secret data from authentication
                    ((CredentialsContainer)result).eraseCredentials();
                }

                eventPublisher.publishAuthenticationSuccess(result);
                return result;
            }

            eventPublisher.publishAuthenticationFailure(lastException, authentication);

            throw lastException;
        }

        //代码略.....................
    }

    //权限查询器
    public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean,
        MessageSourceAware {

        //代码略.....................

        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); //从Ehcache实现的缓存里取userDetail对象

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

                try {
                    //调用DaoAuthenticationProvider类中的方法
                    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");
            }

            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);
        }

        //代码略.....................
    }

    //数据库查询器
    class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider{

        private UserDetailsService userDetailsService;

        //代码略.....................

        protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
                throws AuthenticationException {
            UserDetails loadedUser;

            try {
                //获取用户信息,可以通过实现UserDetailsService接口或继承JdbcDaoImpl类来自定义内部实现
                loadedUser = this.getUserDetailsService().loadUserByUsername(username);//调用自定义类UserDetailsServiceImpl的方法
            }
            catch (DataAccessException repositoryProblem) {
                throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);
            }

            if (loadedUser == null) {
                throw new AuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        }

        protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
            Object salt = null;

            if (this.saltSource != null) {
                salt = this.saltSource.getSalt(userDetails);
            }

            if (authentication.getCredentials() == null) {
                logger.debug("Authentication failed: no credentials provided");

                throw new BadCredentialsException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"),
                        includeDetailsObject ? userDetails : null);
            }

            String presentedPassword = authentication.getCredentials().toString();
            //判断密码是否一致
            if (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {
                logger.debug("Authentication failed: password does not match stored value");

                throw new BadCredentialsException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"),
                        includeDetailsObject ? userDetails : null);
            }
        }

        //代码略.....................
    }

    //自定义类实现查询接口
    public class UserDetailsServiceImpl extends JdbcDaoSupport implements UserDetailsService {

        private String                          authoritiesByUsernameQuery;

        private String                          usersByUsernameQuery;

        //代码略.....................

        public void setAuthoritiesByUsernameQuery(String queryString) {
            authoritiesByUsernameQuery = queryString;
        }

        public void setUsersByUsernameQuery(String usersByUsernameQueryString) {
            this.usersByUsernameQuery = usersByUsernameQueryString;
        }

        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
           List users = loadUsersByUsername(username); //访问数据库查询用户信息

            if (users.size() == 0) {
                logger.debug("Query returned no results for user '" + username + "'");

                throw new UsernameNotFoundException(
                        messages.getMessage("JdbcDaoImpl.notFound", new Object[]{username}, "Username {0} not found"), username);
            }

            UserDetails user = users.get(0); // contains no GrantedAuthority[]

            Set dbAuthsSet = new HashSet();

            if (enableAuthorities) {
                dbAuthsSet.addAll(loadUserAuthorities(user.getUsername())); //查询用户拥有的角色
            }

            if (enableGroups) {
                dbAuthsSet.addAll(loadGroupAuthorities(user.getUsername()));
            }

            List dbAuths = new ArrayList(dbAuthsSet);

            addCustomAuthorities(user.getUsername(), dbAuths);

            if (dbAuths.size() == 0) {
                logger.debug("User '" + username + "' has no authorities and will be treated as 'not found'");

                throw new UsernameNotFoundException(
                        messages.getMessage("JdbcDaoImpl.noAuthority",
                                new Object[] {username}, "User {0} has no GrantedAuthority"), username);
            }

            return createUserDetails(username, user, dbAuths);//返回实现UserDetails接口的对象,将验证信息封装到此对象中
        }

        protected List loadUsersByUsername(String username) {
            return getJdbcTemplate().query(usersByUsernameQuery, new String[]{
                username}, new RowMapper() {

                @Override
                public UserDetails mapRow(ResultSet rs, int rowNum) throws SQLException {
                    String username = rs.getString(1);
                    String password = rs.getString(2);
                    boolean enabled = rs.getBoolean(3);
                    return new userDetailsImpl(username, password, enabled);//返回实现UserDetails接口的对象
                }
            });
        }

        //代码略.....................
    }

    //用户信息bean,必须实现UserDetails接口
    public class userDetailsImpl implements UserDetails {
        //代码略.....................
    }

    //由ExceptionTranslationFilter过滤器调用来进行权限判断
    public abstract class AbstractSecurityInterceptor implements InitializingBean, ApplicationEventPublisherAware,
        MessageSourceAware {

        //代码略.....................

        protected InterceptorStatusToken beforeInvocation(Object object) {

            //这里读取配置FilterSecurityInterceptor的SecurityMetadataSource属性来获取配置的角色,这些属性配置了资源的安全设置
            Collection attributes = this.obtainSecurityMetadataSource().getAttributes(object);

            if (attributes == null) {
                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'");
                }

                publishEvent(new PublicInvocationEvent(object));

                return null; // no further work post-invocation
            }

            //这里从SecurityContextHolder中去取Authentication对象,一般在登录时会放到SecurityContextHolder中去 
            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 {
                //通过投票器判断当前角色是否有权限访问该地址,如果没有权限则抛出异常,调用AffirmativeBased类中的decide的方法
                this.accessDecisionManager.decide(authenticated, object, attributes);
            }
            catch (AccessDeniedException accessDeniedException) {
                //授权不成功向外发布事件
                publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
                        accessDeniedException));

                throw accessDeniedException;
            }

            publishEvent(new AuthorizedEvent(object, attributes, authenticated));

            // 这里构建一个RunAsManager来替代当前的Authentication对象,默认情况下使用的是NullRunAsManager会把SecurityContextHolder中的Authentication对象清空
            Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);

            if (runAs == null) {

                // no further work post-invocation
                return new InterceptorStatusToken(authenticated, false, attributes, object);
            } else {

                SecurityContextHolder.getContext().setAuthentication(runAs);

                // need to revert to token.Authenticated post-invocation
                return new InterceptorStatusToken(authenticated, true, attributes, object);
            }
        }

        //代码略.....................
    }

    //决策器
    public class AffirmativeBased extends AbstractAccessDecisionManager {

        //代码略.....................

        public void decide(Authentication authentication, Object object, Collection 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: //result:1
                    return;

                case AccessDecisionVoter.ACCESS_DENIED: //result:-1
                    //这里对反对票进行计数 
                    deny++;

                    break;

                default:
                    break;
                }
            }
            //如果有反对票,抛出异常,整个授权不通过
            if (deny > 0) {
                throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied",
                        "Access is denied"));
            }

            //这里对弃权票进行处理,看看是全是弃权票的决定情况,默认是不通过,由allowIfAllAbstainDecisions变量控制
            checkAllowIfAllAbstainDecisions();
        }

        //代码略.....................
    }

    //角色投票器
    public class RoleVoter implements AccessDecisionVoter {

        //代码略.....................

        public int vote(Authentication authentication, Object object, Collection attributes) {
            int result = ACCESS_ABSTAIN;
            Collection authorities = extractAuthorities(authentication);

            for (ConfigAttribute attribute : attributes) {//这里取得资源的安全配置  
                if (this.supports(attribute)) { 
                    result = ACCESS_DENIED;

                    //这里对资源配置的安全授权级别进行判断,也就是匹配ROLE为前缀的角色配置   
                    //遍历每个配置属性,如果其中一个匹配该主体持有的GrantedAuthority,则访问被允许。
                    //当前用户拥有的角色集合,当有任何一个角色满足时授予权限
                    for (GrantedAuthority authority : authorities) { 
                        if (attribute.getAttribute().equals(authority.getAuthority())) {
                            return ACCESS_GRANTED;
                        }
                    }
                }
            }

            return result;
        }

        Collection extractAuthorities(Authentication authentication) {
            return authentication.getAuthorities();
        }

        //代码略.....................
    }

来源:http://ddrv.cn

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » spring-security3(二)源码分析

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏