文章目录
项目源码地址 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表里多了一条数据如下:
至此第三方登陆过程中关于注册相关的功能开发已经全部介绍完毕!!!