述
社交账号登录的逻辑完成之后,再来重构一下用户注册的逻辑, 回顾一下之前浏览器端写的注册逻辑
- 用户授权第三方登录之后,social 拿到用户的信息
- 查询 userconnection 表,看有没有对应的数据
- 没有找到的话,跳转到我们配置的注册页,默认是
/signUp
- 到注册页填写信息,发送请求,然后调用数据库,最终拿到我们业务系统中的唯一id
- 调用 social 提供的
providerSignInUtils
做绑定
大概流程就是这样, 在app环境中,这个流程会有一些问题,浏览器没有cookie,也就没有 JSESSIONID
,第一次第三方登录请求放到session中的数据,在注册的请求中是拿不到的,而且这个跳转是重定向到登录页了,在App中应该返回一个json的提示信息即可
解决思路
session中拿不到的数据,我们可以放到redis里面, 重定向到登录页的时候,我们可以让他重定向到一个请求中去,然后返回json的数据, 用户调用注册请求的时候,从redis中获取之前的缓存,更新到数据库就可以了
注意要把之前配置的自动注册的类先关掉
修改注册跳转路径
我们之前配置的注册地址是在 SocialConfig
中,通过 @Bean 注入 securitySocialConfigurer
的时候配置的,如下
这个配置在浏览器中是没有问题的,不需要修改,只需要修改在app环境中的配置, app项目中,新建类 SpringSocialConfigurerPostProcessor
代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33public class SpringSocialConfigurerPostProcessor implements BeanPostProcessor
{
/**
* 任何bean初始化之前做的事情
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
/**
* 任何bean初始化之后做的事情
* 我们这里要做的就是在 SpringSocialConfigurer 加载完了之后,把他的注册跳转的url改掉
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (StringUtils.equals(beanName,"securitySocialConfigurer")){
MySpringSocialConfigurer mySpringSocialConfigurer = (MySpringSocialConfigurer) bean;
mySpringSocialConfigurer.signupUrl(SecurityConstants.DEFAULT_APP_SIGNUP_URL);
return mySpringSocialConfigurer;
}
return null;
}
}
这个类的作用就是,在 SocialConfig
中,通过 @Bean 注入的 securitySocialConfigurer
这个类,加载完成之后,修改一下他的 signupUrl
改为redis存储
app项目中,新建一个 AppSignUpUtil
,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60@Component
public class AppSignUpUtil {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private UsersConnectionRepository usersConnectionRepository;
@Autowired
private ConnectionFactoryLocator connectionFactoryLocator;
/**
* 将第三方用户信息放到redis缓存中去
* @param request
* @param connectionData
*/
public void saveConnectionData(HttpServletRequest request, ConnectionData connectionData) {
redisTemplate.opsForValue().set(getRedisKey(request), connectionData, 10, TimeUnit.MINUTES);
}
/**
* 绑定业务系统中的用户
* @param request
* @param userId
*/
public void doPostSignUp(HttpServletRequest request, String userId){
// 判断缓存中有没有存第三方的用户信息
String redisKey = getRedisKey(request);
if (!redisTemplate.hasKey(redisKey)) {
throw new AppSerectException("无法找到缓存中的用户社交账号信息");
}
// 从缓存中取出来
ConnectionData connectionData = (ConnectionData) redisTemplate.opsForValue().get(redisKey);
// ConnectionData对象转成 connection对象
Connection<?> connection = connectionFactoryLocator.getConnectionFactory(connectionData.getProviderId()).createConnection(connectionData);
// 操作数据库 创建connection信息
usersConnectionRepository.createConnectionRepository(userId).addConnection(connection);
}
/**
* 获取缓存的key
* @param request
* @return
*/
private String getRedisKey(HttpServletRequest request) {
// 从请求中获取设备id
String deviceId = request.getHeader("deviceId");
if (StringUtils.isBlank(deviceId)) {
throw new AppSerectException("deviceId不能为空");
}
return Constants.SOCIAL_USER_INFO_KEY_PREFIX + deviceId;
}
}
这里主要是两个方法,一个放缓存,一个是绑定业务系统与第三方用户的信息, 跟我们之前用的 ProviderSignInUtils
是一样的
controller处理
上面处理了 APP 的跳转路径,对应的应该有一个 controller 去处理具体的逻辑, 新建 SocialController
,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27@RestController
public class SocialController {
@Autowired
private ProviderSignInUtils providerSignInUtils;
@Autowired
private AppSignUpUtil appSignUpUtil;
@GetMapping(SecurityConstants.DEFAULT_APP_SIGNUP_URL)
public SocialUserInfo getSocialUserInfo(HttpServletRequest request){
SocialUserInfo userInfo = new SocialUserInfo();
Connection<?> connection = providerSignInUtils.getConnectionFromSession(new ServletWebRequest(request));
userInfo.setProviderId(connection.getKey().getProviderId());
userInfo.setProviderUserId(connection.getKey().getProviderUserId());
userInfo.setNickname(connection.getDisplayName());
userInfo.setHeadimg(connection.getImageUrl());
// 存到redis中去
appSignUpUtil.saveConnectionData(request, connection.createData());
return userInfo;
}
}
这里其实就是把第三方的用户信息存到缓存中去
url拦截
记得把配置的注册路径配置到不需要权限的集合里面去
注册接口修改
浏览器的注册接口使用的是1
providerSignInUtils.doPostSignUp(String.valueOf(userId), new ServletWebRequest(request));
在 App 中,需要改成我们刚才创建的 AppSignUpUtil
1
appSignUpUtil.doPostSignUp(request,String.valueOf(userId));
测试的话,还是按照之前的流程,先用web环境拿到code,然后切换浏览器环境