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


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

1 概述

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

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


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

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

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,实际上深刻该代码深处,能够看到其逻辑不止这么简单,而本篇文章标题所说的免注册的解决方法也正是隐藏于此。web

  • 先看一下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 方法就可让用户在进行第三方登录的过程当中,再也不走注册新用户的逻辑,而直接登录咱们的网站了。spring


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表里多了一条数据以下:
在这里插入图片描述sql


至此第三方登录过程当中关于注册相关的功能开发已经所有介绍完毕!!!微信

相关文章
相关标签/搜索