Spring Social 认证流程源码详解

Spring Security 过滤链

在这里插入图片描述

认证流程源码详解

SocialAuthenticationFilter会将请求拦截下来然后将整个流程走完。进而去实现第三方登录。详细流程如下。
在这里插入图片描述
我们来看一下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协议的授权码模式。
在这里插入图片描述

 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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值