Spring Security-27-注册逻辑重构

社交账号登录的逻辑完成之后,再来重构一下用户注册的逻辑, 回顾一下之前浏览器端写的注册逻辑

  1. 用户授权第三方登录之后,social 拿到用户的信息
  2. 查询 userconnection 表,看有没有对应的数据
  3. 没有找到的话,跳转到我们配置的注册页,默认是 /signUp
  4. 到注册页填写信息,发送请求,然后调用数据库,最终拿到我们业务系统中的唯一id
  5. 调用 social 提供的 providerSignInUtils 做绑定

大概流程就是这样, 在app环境中,这个流程会有一些问题,浏览器没有cookie,也就没有 JSESSIONID ,第一次第三方登录请求放到session中的数据,在注册的请求中是拿不到的,而且这个跳转是重定向到登录页了,在App中应该返回一个json的提示信息即可

解决思路

session中拿不到的数据,我们可以放到redis里面, 重定向到登录页的时候,我们可以让他重定向到一个请求中去,然后返回json的数据, 用户调用注册请求的时候,从redis中获取之前的缓存,更新到数据库就可以了

注意要把之前配置的自动注册的类先关掉

修改注册跳转路径

我们之前配置的注册地址是在 SocialConfig 中,通过 @Bean 注入 securitySocialConfigurer 的时候配置的,如下
image

这个配置在浏览器中是没有问题的,不需要修改,只需要修改在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
33
public 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,然后切换浏览器环境

本文代码传送门