Spring Security-19-绑定和解绑处理

在使用第三方登录时,有一个常见的场景就是用户登录完成之后,再去绑定/解绑第三方社交账号

绑定也是需要走一下 OAuth 流程,用户授权完成之后,将第三方的用户信息与当前系统的用户信息做一个绑定, 这里和登录不同的地方是, 登录是不知道当前用户信息的,需要先拿到第三方用户信息然后去 userconnection 表里查询系统的用户信息, 而绑定是用户已经登录之后的操作,也就是说已经知道当前业务系统中的用户了,然后授权之后跟当前登录的用户做绑定

解绑操作的话其实就是删除 userconnection 表里的一条数据

Spring Social 对这种场景提供了默认的支持,下面来看一下如何使用

绑定

Spring Social 提供了一个 ConnectController 里面可以查询到当前用户的所有的绑定信息,源码如下:
image

这里就是从数据库里面找到所有的绑定信息,然后放到Model里面,最后返回一个 connectView(), 这个方法代码如下:
image

这里就是返回了一个 connect/status 的视图, 但是这个视图 Social 并没有提供,需要我们自己去实现,所以我们只需要实现这个视图就可以了

connect/status 获取绑定状态

新建类 ConnectionStatusView 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Component("connect/status")
public class ConnectionStatusView extends AbstractView {

@Autowired
private ObjectMapper objectMapper;

@Override
protected void renderMergedOutputModel(Map<String, Object> map, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 拿到所有的绑定信息
Map<String, List<Connection<?>>> connections = (Map<String, List<Connection<?>>>) map.get("connectionMap");

// 最终结果是 [第三方providerId:是否绑定]
Map<String, Boolean> result = new HashMap<>(connections.size());
for (String key : connections.keySet()) {
result.put(key, CollectionUtils.isNotEmpty(connections.get(key)));
}

// 返回给页面
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(result));

}

}

这里我们只需要给前端返回一个是否绑定的标识就好了

可以启动项目请求 connect 看下效果
image

绑定的实现

绑定同样 Social 也提供了一个接口 POST /connect//{providerId} 源码如下:
image
这里其实就是重定向到第三方的授权页面, 然后第三方授权成功后的回调是

image

这个方法主要是往 userconnection 表里面添加数据,绑定完成之后最终也会返回一个视图,名字是 connect/{providerId}Connected

在实现之前,首先写一个html页面用来做绑定

1
2
3
<form action="/connect/weChat" method="post">
<button type="submit">绑定微信</button>
</form>

只需要一个表单即可,这里首先是前半段 /connect 这个是固定的, 后面的 weChatproviderId

然后把视图实现一下, 新建类 ConnectedView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ConnectView extends AbstractView {

@Autowired
private ObjectMapper objectMapper = new ObjectMapper();

@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

Map<String, Object> result = new HashMap<>(1);


response.setContentType("application/json;charset=UTF-8");

result.put("message", "绑定成功");
response.getWriter().write(objectMapper.writeValueAsString(result));
}

}

这里就是返回了一个绑定成功的信息, 然后这个类并没有加 @Component 注解, 因为所有的第三方绑定成功都是可以重用的

已微信为例,现在视图的名称应该是 connect/weChatConnected ,这个视图我们可以通过 @Bean 的方式注入到 Spring 容器中去, 在 WeChatAutoConfig 中加入以下内容

1
2
3
4
5
@Bean("connect/weChatConnected")
@ConditionalOnMissingBean(name = "weChatConnectView" )
public View weChatConnectView(){
return new ConnectView();
}

这样就可以了 @ConditionalOnMissingBean 的用法和之前是一样的

这时候可以启动项目,先登陆,然后进去绑定页面,点绑定微信,然后授权,如果返回是我们视图中的信息就成功了, 同时 userconnection 表里面会多一条信息

解绑实现

解绑的接口是 DELETE /connect/{providerId}
image

他其实就是从 userconnection 中删除一条数据,然后最后他也会返回一个视图,名称是 connect/{providerId}Connect

这里这个视图是可以和上面的绑定的视图用一个, 修改 ConnectView ,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

Map<String, Object> result = new HashMap<>(1);


response.setContentType("application/json;charset=UTF-8");

if (model.get("connection") == null) {
result.put("message", "解绑成功");

} else {
result.put("message", "绑定成功");
}

response.getWriter().write(objectMapper.writeValueAsString(result));
}

然后 WeChatAutoConfig 修改如下:

1
2
3
4
5
@Bean({"connect/weChatConnected","connect/weChatConnect"})
@ConditionalOnMissingBean(name = "weChatConnectView" )
public View weChatConnectView(){
return new ConnectView();
}

这里绑定和解绑就是判断一下,有没有 connection, 然后 @Bean 注入的时候,注入两个就ok了

上面例子都是以微信为例, QQ的可以自己实现一下

本文代码传送门