欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Social 认证流程源码详解

程序员文章站 2022-03-08 20:13:10
...

Spring Security 过滤链

Spring Social 认证流程源码详解

认证流程源码详解

SocialAuthenticationFilter会将请求拦截下来然后将整个流程走完。进而去实现第三方登录。详细流程如下。
Spring Social 认证流程源码详解
我们来看一下SocialAuthenticationFilter的源码

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.detectRejection(request)) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("A rejection was detected. Failing authentication.");
            }

            throw new SocialAuthenticationException("Authentication failed because user rejected authorization.");
        } else {
            Authentication auth = null;
            //已注册服务提供商Id
            Set<String> authProviders = this.authServiceLocator.registeredAuthenticationProviderIds();
            //请求中的providerId
            String authProviderId = this.getRequestedProviderId(request);
            if (!authProviders.isEmpty() && authProviderId != null && authProviders.contains(authProviderId)) {
            //查找处理该ProviderId的 SocialAuthenticationService
                SocialAuthenticationService<?> authService = this.authServiceLocator.getAuthenticationService(authProviderId);
                auth = this.attemptAuthService(authService, request, response);
                if (auth == null) {
                    throw new AuthenticationServiceException("authentication failed");
                }
            }

            return auth;
        }
    }

接着调用

this.attemptAuthService(authService, request, response);

看一下源码

 private Authentication attemptAuthService(SocialAuthenticationService<?> authService, HttpServletRequest request, HttpServletResponse response) throws SocialAuthenticationRedirectException, AuthenticationException {
        // 封装SocialAuthenticationToken对象
        SocialAuthenticationToken token = authService.getAuthToken(request, response);
        if (token == null) {
            return null;
        } else {
            Assert.notNull(token.getConnection());
            Authentication auth = this.getAuthentication();
            //判断是否认证通过
            if (auth != null && auth.isAuthenticated()) { //认证通过
                this.addConnection(authService, request, token, auth);
                return null;
            } else { 
                //进行认证
                return this.doAuthentication(authService, request, token);
            }
        }
    }

接下来调用

authService.getAuthToken(request, response);

这里具体会调用 OAuth2AuthenticationService的getAuthToken(request, response)方法。
我们来看一下getAuthToken的源码,这里会涉及到OAauth协议的授权码模式。
Spring Social 认证流程源码详解

 public SocialAuthenticationToken getAuthToken(HttpServletRequest request, HttpServletResponse response) throws SocialAuthenticationRedirectException {
        String code = request.getParameter("code");
        //判断受否获得授权码
        if (!StringUtils.hasText(code)) { //没有获取授权码
            OAuth2Parameters params = new OAuth2Parameters();
            //设置认证服务器地址
            params.setRedirectUri(this.buildReturnToUrl(request));
            this.setScope(request, params);
            params.add("state", this.generateState(this.connectionFactory, request));
            this.addCustomParameters(params);
            //这个异常会把用户导向认证服务器
            throw new SocialAuthenticationRedirectException(this.getConnectionFactory().getOAuthOperations().buildAuthenticateUrl(params));
        } else if (StringUtils.hasText(code)) { //已经获取了授权码
            try {
                String returnToUrl = this.buildReturnToUrl(request);
                // 申请令牌
                AccessGrant accessGrant = this.getConnectionFactory().getOAuthOperations().exchangeForAccess(code, returnToUrl, (MultiValueMap)null);
                // 获取用户信息封装到Connection对象中
                Connection<S> connection = this.getConnectionFactory().createConnection(accessGrant);
                //把Connection封装到SocialAuthenticationToken中,并设置为未认证
                return new SocialAuthenticationToken(connection, (Map)null);
            } catch (RestClientException var7) {
                this.logger.debug("failed to exchange for access", var7);
                return null;
            }
        } else {
            return null;
        }
    }

其中 this.getConnectionFactory() 将会调用用户自己实现的针对具体服务提供商的ConnectionFactory。具体可参考
SpringSocial原理:https://blog.csdn.net/weixin_39869513/article/details/105547445

接下来调用,进行认证

this.doAuthentication(authService, request, token);

看一下源码

private Authentication doAuthentication(SocialAuthenticationService<?> authService, HttpServletRequest request, SocialAuthenticationToken token) {
        try {
            if (!authService.getConnectionCardinality().isAuthenticatePossible()) {
                return null;
            } else {
                token.setDetails(this.authenticationDetailsSource.buildDetails(request));
                //验证逻辑
                Authentication success = this.getAuthenticationManager().authenticate(token);
                Assert.isInstanceOf(SocialUserDetails.class, success.getPrincipal(), "unexpected principle type");
                this.updateConnections(authService, token, success);
                return success;
            }
        } catch (BadCredentialsException var5) {
         //导向注册页
            if (this.signupUrl != null) {
                this.sessionStrategy.setAttribute(new ServletWebRequest(request), ProviderSignInAttempt.SESSION_ATTRIBUTE, new ProviderSignInAttempt(token.getConnection()));
                throw new SocialAuthenticationRedirectException(this.buildSignupUrl(request));
            } else {
                throw var5;
            }
        }
    }

接着调用

this.getAuthenticationManager().authenticate(token);

这里有一个AuthenticationManager,但是真正调用的是ProviderManager。

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
    public Authentication authenticate(Authentication authentication) throws AuthenticationException { 
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        Authentication result = null;
        boolean debug = logger.isDebugEnabled();
        Iterator var6 = this.getProviders().iterator();

        while(var6.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var6.next();
            // 1.判断是否有provider支持该Authentication
            if (provider.supports(toTest)) {
                // 2. 真正的逻辑判断
                result = provider.authenticate(authentication);
            }
    }
}

  1. 这里首先通过provider判断是否支持当前传入进来的Authentication,目前我们使用的是SocialAuthenticationToken。
  2. 根据我们目前所使用的SocialAuthenticationToken,provider对应的是SocialAuthenticationProvider。

下面接着看 provider.authenticate(authentication)方法

 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(SocialAuthenticationToken.class, authentication, "unsupported authentication type");
        Assert.isTrue(!authentication.isAuthenticated(), "already authenticated");
        SocialAuthenticationToken authToken = (SocialAuthenticationToken)authentication;
        String providerId = authToken.getProviderId();
        Connection<?> connection = authToken.getConnection();
        // 1.去数据库查询服务提供商在第三方应用里对应用户id
        String userId = this.toUserId(connection)
        // 如果为空,BadCredentialsException异常被捕获后,将会把用户重定向到注册页
        if (userId == null) {
            throw new BadCredentialsException("Unknown access token");
        } else {
        //2.去获取UserDetails
            UserDetails userDetails = this.userDetailsService.loadUserByUserId(userId);
            if (userDetails == null) {
                throw new UsernameNotFoundException("Unknown connected account id");
            } else {
        //3. 返回真正的经过认证的Authentication 
                return new SocialAuthenticationToken(connection, userDetails, authToken.getProviderAccountData(), this.getAuthorities(providerId, userDetails));
            }
        }
    }
  1. 看一下 this.toUserId(connection)
    protected String toUserId(Connection<?> connection) {
        List<String> userIds = this.usersConnectionRepository.findUserIdsWithConnection(connection);
        return userIds.size() == 1 ? (String)userIds.iterator().next() : null;
    }

这里调用

this.usersConnectionRepository.findUserIdsWithConnection(connection)

我们这里以UsersConnectionRepository的实现类JdbcUsersConnectionRepositor来举例

    public List<String> findUserIdsWithConnection(Connection<?> connection) {
        ConnectionKey key = connection.getKey();
        //通过providerId和 providerUserId查询 userId
        List<String> localUserIds = this.jdbcTemplate.queryForList("select userId from " + this.tablePrefix + "UserConnection where providerId = ? and providerUserId = ?", String.class, new Object[]{key.getProviderId(), key.getProviderUserId()});
        //如果第三应用中还没有该用户信息,可以提供 connectionSignUp的实现来指定该用户的userId,实现自动注册用户,不会跳到注册页面
        if (localUserIds.size() == 0 && this.connectionSignUp != null) {
            String newUserId = this.connectionSignUp.execute(connection);
            if (newUserId != null) {
            //把用户信息存到数据库中
                this.createConnectionRepository(newUserId).addConnection(connection);
                return Arrays.asList(newUserId);
            }
        }
        return localUserIds;
    }
  1. 去调用自己实现的SocialUserDetailsService,返回UserDetails
  2. 返回真正的经过认证的Authentication

拿到经过认证的Authentication之后,会再去调用successHandler。或者未通过认证,去调用failureHandler

相关标签: Spring Security