springsocial/oauth2---第三方登陆之QQ登陆8【注册逻辑之免注册+springsocial源码解读④】


项目源码地址 https://github.com/nieandsun/security

1 概述

正如上文所诉,现实中,我们肯定碰到过很多网站或APP,虽然之前我们从未访问过这些网站,但是当我们用QQ或微信等进行登陆时,却可以直接登陆成功,根本不会被提醒让我们再注册一个新的用户。其实我觉得这样体验好像反而更好一些☺。

在springsocial/springsecurity的逻辑里,这要如何去实现呢?我们必须还是要从springsocial/springsecurity的源码里去探寻答案。


2 从源码中探寻跳过注册逻辑的实现方法

在《springsocial/oauth2—第三方登陆之QQ登陆6【注册逻辑之/signup错误解决+springsocial源码解读②】》那篇文章里我们讲到了SocialAuthenticationProvider中的authenticate方法,其部分源码如下:

		Connection<?> connection = authToken.getConnection();
		//拿着Connection对象去userconnection表里去查相应的userId,由于我们的系统里暂时还没有一个用户,
		//所以查到的userId肯定为null----》为null就会报出BadCredentialsException---》然后就会回到上面的类
		//让你跳转到一个XXX/signup的url----》其实说白了就是引导用户进入注册用户的页面(这一块需要我们自己实现)	
		String userId = toUserId(connection);
		if (userId == null) {
			throw new BadCredentialsException("Unknown access token");
		}

当时文章只是说由于我们的系统里还没有一个用户,所以查到的userId肯定为null,实际上深入该代码深处,可以看到其逻辑不止这么简单,而本篇文章标题所说的免注册的解决方法也正是隐藏于此。

  • 先看一下toUserId的具体代码:
protected String toUserId(Connection<?> connection) {
	//拿着Connection对象去找userIds
	List<String> userIds = usersConnectionRepository.findUserIdsWithConnection(connection);
	// only if a single userId is connected to this providerUserId
	return (userIds.size() == 1) ? userIds.iterator().next() : null;
}
  • 再看一下findUserIdsWithConnection的具体代码(代码所在类JdbcUsersConnectionRepository):
	public List<String> findUserIdsWithConnection(Connection<?> connection) {
		ConnectionKey key = connection.getKey();
		//通过providerId和providerUserId(openId)去userconnection表里查找useIds
		List<String> localUserIds = jdbcTemplate.queryForList("select userId from " + tablePrefix + "UserConnection where providerId = ? and providerUserId = ?", String.class, key.getProviderId(), key.getProviderUserId());	
		//如果没找到useId(没关联过QQ肯定找不到userId)且connectionSignUp不为null 
		if (localUserIds.size() == 0 && connectionSignUp != null) {
			//调用connectionSignUp的execute方法生成一个useId
			String newUserId = connectionSignUp.execute(connection);
			//如果生成成功
			if (newUserId != null)
			{	//则将生成的userId连同Connection对象,插入到userconnection表,即利用代码“偷偷地”注册一个用户
				createConnectionRepository(newUserId).addConnection(connection);
				//将生成的useId返回 
				return Arrays.asList(newUserId);
			}
		}
		//走到这里说明通过providerId和providerUserId(openId)找到了useId
		return localUserIds;
	}

通过上面的源代码解读,我想大家肯定已经知道,只要我们在JdbcUsersConnectionRepository类里注入一个 connectionSignUp ,并实现其 execute 方法就可以让用户在进行第三方登陆的过程中,不再走注册新用户的逻辑,而直接登陆我们的网站了。


3 具体代码实现

3.1 编写ConnectionSignUp的具体实现

package com.nrsc.security.server.security;

import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionSignUp;
import org.springframework.stereotype.Component;

/**
 * @author : Sun Chuan
 * @date : 2019/9/14 23:29
 * Description:ConnectionSignUp类,需要注入到JdbcUsersConnectionRepository类里,
 * 以实现第三方登陆时免注册的逻辑功能
 */
@Component
public class DemoConnectionSignUp implements ConnectionSignUp {
    @Override
    public String execute(Connection<?> connection) {
        //真实项目中: TODO
        //这里其实应该向我们的用户业务表(user表)里插入一条数据
        //可以将插入user数据后的主键作为userId进行返回

        //这里为了简单起见,我就直接将connection对象中的displayName作为useId进行返回了
        return connection.getDisplayName();
    }
}

3.2 将编写的ConnectionSignUp具体实现类注入到JdbcUsersConnectionRepository

  • 这里将JdbcUsersConnectionRepository类注入到spring容器的全部代码贴出如下:
package com.nrsc.security.core.social;

import com.nrsc.security.core.properties.NrscSecurityProperties;
import com.nrsc.security.core.social.jdbc.NrscJdbcUsersConnectionRepository;
import com.nrsc.security.core.social.qq.NrscSpringSocialConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.social.config.annotation.EnableSocial;
import org.springframework.social.config.annotation.SocialConfigurerAdapter;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.ConnectionSignUp;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.web.ProviderSignInUtils;
import org.springframework.social.security.SpringSocialConfigurer;

import javax.sql.DataSource;

/**
 * @author : Sun Chuan
 * @date : 2019/8/7 20:57
 * Description:
 * UsersConnectionRepository的实现类,用来拿着Connection对象查找UserConnection表中是否与之相对应的userId
 * userId就是我们系统中的唯一标识,这个应该由各个系统自己根据业务去指定,比如说你系统里的username是唯一的,
 * 则这个useId就可以是username
 */
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {

    @Autowired
    private DataSource dataSource;
    @Autowired
    private NrscSecurityProperties nrscSecurityProperties;
    /**
     * 并不一定所有的系统都会在用户进行第三方登陆时“偷偷地”给用户注册一个新用户
     * 所以这里需要标明required = false
     */
    @Autowired(required = false)
    private ConnectionSignUp connectionSignUp;


    @Override
    public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {

        /**
         * 第二个参数的作用:根据条件查找该用哪个ConnectionFactory来构建Connection对象
         * 第三个参数的作用: 对插入到userconnection表中的数据进行加密和解密
         */
        NrscJdbcUsersConnectionRepository repository = new NrscJdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
        //设置userconnection表的前缀
        repository.setTablePrefix("nrsc_");

        if (connectionSignUp != null) {
            //如果有spring容器里connectionSignUp这个bean时,将其注入到UsersConnectionRepository
            repository.setConnectionSignUp(connectionSignUp);
        }
        return repository;
    }

    /**
     * 通过apply()该实例,可以将SocialAuthenticationFilter加入到spring-security的过滤器链
     */
    @Bean
    public SpringSocialConfigurer nrscSocialSecurityConfig() {
        // 默认配置类,进行组件的组装
        // 包括了过滤器SocialAuthenticationFilter 添加到security过滤链中
        String filterProcessesUrl = nrscSecurityProperties.getSocial().getFilterProcessesUrl();
        NrscSpringSocialConfigurer configurer = new NrscSpringSocialConfigurer(filterProcessesUrl);

        //指定SpringSocial/SpringSecurity跳向注册页面时的url为我们配置的url
        configurer.signupUrl(nrscSecurityProperties.getBrowser().getSignUpUrl());
        return configurer;
    }

    /**
     * ProviderSignInUtils有两个作用:
     * (1)从session里获取封装了第三方用户信息的Connection对象
     * (2)将注册的用户信息与第三方用户信息(QQ信息)建立关系并将该关系插入到userconnection表里
     * <p>
     * 需要的两个参数:
     * (1)ConnectionFactoryLocator 可以直接注册进来,用来定位ConnectionFactory
     * (2)UsersConnectionRepository----》getUsersConnectionRepository(connectionFactoryLocator)
     * 即我们配置的用来处理userconnection表的bean
     *
     * @param connectionFactoryLocator
     * @return
     */
    @Bean
    public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator) {
        return new ProviderSignInUtils(connectionFactoryLocator,
                getUsersConnectionRepository(connectionFactoryLocator)) {
        };
    }
}

4 测试

(1)QQ登陆之前,userconnection表里什么都没有:
在这里插入图片描述
(2)点击QQ登陆—》进行QQ授权进入到如下断点中:
在这里插入图片描述
(3)将代码放行发现可以跳到我设置的主页,如下:
在这里插入图片描述
并且userconnection表里多了一条数据如下:
在这里插入图片描述


至此第三方登陆过程中关于注册相关的功能开发已经全部介绍完毕!!!


  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值