文章目录

  • 1 事先声明
  • 2 ConnectController简介
  • 3 获取当前用户所有第三方账号的绑定状态
    • 3.1 当前用户所有第三方账号绑定状态绑定状态的处理视图
    • 3.2 测试
  • 4 将当前用户与第三方账号进行绑定
    • 4.1 绑定微信
    • 4.2 QQ绑定 --- redirect uri is illegal(100010)错误分析
  • 5 解绑
    • 5.1 在页面利用ajax发送delete请求 --- 报ERR_TOO_MANY_REDIRECTS错误
    • 5.2 利用restlet_client插件发送delete请求 --- 302异常
    • 5.3 使用form形式发送delete请求 --- 终于进入解绑成功后的逻辑

项目源码地址 https://github.com/nieandsun/security
注意: 源码中已经包含了微信登陆功能☺☺☺

1 事先声明

之前在《springsocial/oauth2—第三方登陆之QQ登陆7【注册逻辑之mysql里rank为关键字问题解决+springsocial源码解读③】》这篇文章里我讲到过一种绑定的应用场景,即登陆者第一次用QQ登陆一个网站,假如这个网站没有在程序里“偷偷地”为登陆者创建一个用户并将该用户和QQ的关联关系插入到userconnection表的话,按照springsocial/springsecurity的默认逻辑来讲会跳到一个注册/绑定页面。假如登陆者在这个网站里曾经注册过用户的话,那么他就可以直接输入自己的用户名+密码并点击绑定,来绑定已有账户和QQ之间的关系。

但是本文讲的绑定和解绑并不适用于这个场景,因为本文讲的绑定和解绑是在用户已经登陆的情况下(即用户信息已经存在于session中),对QQ,微信等进行绑定和解绑,而上面的场景下session里并没有用户信息。

这里简单畅想一下我说的这种场景的绑定的实现步骤:

  • 在进入绑定页面前session里已经存了封装了QQ信息的Connection对象
  • 输入用户名+密码后,点击绑定按钮
  • 点击绑定按钮后第一个要做的事其实是用户名+密码登陆
  • 登陆成功后应该在走登陆成功事件之前从session里取出Connection对象和用户名或用户id
  • 利用ProviderSignInUtils工具类将Connection对象+用户名或用户id建立关系并插入到userConnection表 —》完成绑定工作

2 ConnectController简介

ConnectController是springsocial为我们提供的一个专门用于处理绑定和解绑的Controller类。在该类里主要定义了如下几个方法:

  • 获取当前用户所有第三方账号的绑定状态的方法
  • 将当前用户与第三方账号进行绑定的方法
  • 解绑的方法

这个Controller有如下几个特点:

  • (1)该Controller里的方法只提供了数据,并没有提供视图,比如说下面的方法为获取当前用户所有第三方账号绑定状态的Controller方法,它虽然向Model对象里写了数据并指定了返回视图的名称 —》connect/status,但并没提供该视图。
 @RequestMapping(method=RequestMethod.GET)public String connectionStatus(NativeWebRequest request, Model model) {setNoCache(request);processFlash(request, model);Map<String, List<Connection<?>>> connections = connectionRepository.findAllConnections();//将providerIds放到model中---Set<String>model.addAttribute("providerIds", connectionFactoryLocator.registeredProviderIds()); //将Connection放到model中   model.addAttribute("connectionMap", connections);//下面返回视图名其实是connect/status----但是springsocial并没有提供该名称的视图//需要我们自己写代码实现该视图,并返回给前端特定的数据return connectView();}
  • (2) 该Controller提供的方法都是基于session的 。
  • (3) 该Controller提供的方法如解绑貌似只能用form请求(我试着用ajax请求搞了好久,一直没成功!!!)

基于以上3个特点,其实我感觉在真实的项目里,为了灵活应对项目需求,我们完全可以模仿这个ConnectController来写一个适用于我们的项目的Controller类来处理绑定和解绑的业务。但本文仅仅抛砖引玉的讲讲如何利用好springsocial提供的这个ConnectController类来实现绑定和解绑的工作。

3 获取当前用户所有第三方账号的绑定状态

3.1 当前用户所有第三方账号绑定状态绑定状态的处理视图

如2中所说,其实ConnectController提供的获取当前用户所有第三方账号绑定状态的方法已经为我们封装好了数据,我们只要写一个视图,将数据接收并返回给浏览器就好了,示例代码如下:

package com.nrsc.security.core.social;import com.fasterxml.jackson.databind.ObjectMapper;
import com.nrsc.security.utils.ResultVOUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.connect.Connection;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.AbstractView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;/*** @author : Sun Chuan* @date : 2019/9/17 23:22* Description: 获取当前用户所有第三方账号的绑定状态的视图*/
@Component("connect/status")
@Slf4j
public class NrscConnectionStatusView extends AbstractView {@Autowiredprivate ObjectMapper objectMapper;@Overrideprotected void renderMergedOutputModel(Map<String, Object> map, HttpServletRequest request,HttpServletResponse response) throws Exception {//取出所有的Connection对象Map<String, List<Connection<?>>> connections = (Map<String, List<Connection<?>>>) map.get("connectionMap");//取出所有的providerIdSet<String> providerIds = (Set<String>) map.get("providerIds");log.info("providerIds:{}",providerIds);//封装当前用户的第三方账号绑定状态 key为providerId , value为true或falseMap<String, Boolean> result = new HashMap<>();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(ResultVOUtil.success(result)));}
}

3.2 测试

如下图,userconnection表里有如下两条数据(nrsc绑定了QQ,yoyo绑定了微信)。

若以用户nrsc进行登陆时,调用www.pinzhi365.com/connect,则得到结果如下,证明我们配置的视图起作用了。

4 将当前用户与第三方账号进行绑定

4.1 绑定微信

需要一个post请求,请求url为/connect/{providerId},以微信为例页面可按照如下方式发送请求

  <form action="/connect/weixin" method="post"><button type="submit">绑定微信</button></form>

当ConnectController接收到该post请求后会拼接一个重定向到微信授权页面的url,并根据该url重定向到微信授权页面。用户在授权页面进行扫码授权后微信会利用url中的redirect_uri(回调地址)发送一个get请求回调我们的项目(接收这个回调的Controller也在ConnectController中,有兴趣的可以跟一下源码)—》 接着我们的项目会再去请求微信获取微信用户信息并将其封装成一个Connection对象 —》 然后将Connection对象和session中的用户信息进行关联,并将该关联关系存到userconnection表里(其实这一步已经完成了绑定) —》 接着调用一个视图,视图名为connect/+providerId+Connected(当然spingsocial没提供该视图)。

下面提供一种各个第三方账号统一使用一个绑定和解绑逻辑的方法

  • 绑定成功和绑定失败的处理逻辑
package com.nrsc.security.core.social;import com.fasterxml.jackson.databind.ObjectMapper;
import com.nrsc.security.utils.ResultVOUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.view.AbstractView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;/*** @author : Sun Chuan* @date : 2019/9/17 23:41* Description: 由于微信,QQ,微博等解绑和绑定都想用这个视图,但providerId不可能一样* 所以这里不能直接用@Component注解将其写死*/
public class NrscConnectView extends AbstractView {@Autowiredprivate ObjectMapper objectMapper;@Overrideprotected void renderMergedOutputModel(Map<String, Object> map, HttpServletRequest request,HttpServletResponse response) throws Exception {response.setContentType("application/json;charset=UTF-8");//如果map(Model对象)里有Connection对象,则表示绑定,否则表示解绑if (map.get("connections") == null) {response.getWriter().write(objectMapper.writeValueAsString(ResultVOUtil.success("解绑成功")));} else {response.getWriter().write(objectMapper.writeValueAsString(ResultVOUtil.success("绑定成功")));}}
}
  • 微信绑定和解绑指定使用上面的逻辑
    /**** connect/weixinConnected 绑定成功的视图* connect/weixinConnect 解绑成功的视图** 两个视图可以写在一起,通过判断Model对象里有没有Connection对象来确定究竟是解绑还是绑定*/@Bean({"connect/weixinConnect", "connect/weixinConnected"})//下面的注解的意思是当程序里有名字为weixinConnectedView的bean// 我写的默认的weixinConnectedView这个bean不会生效,也就是你可以写一个更好的bean来覆盖掉我的@ConditionalOnMissingBean(name = "weixinConnectedView")public View weixinConnectedView() {return new NrscConnectView();}

请读者自行测试。

4.2 QQ绑定 — redirect uri is illegal(100010)错误分析

在进行完微信绑定之后,我试着写了QQ绑定的代码,发现点击QQ绑定时又出现了 redirect uri is illegal(100010)错误,追踪源代码发现在进行QQ绑定时拼接的跳向QQ授权页面的url如下:

https://graph.qq.com/oauth2.0/authorize?client_id=100550231&response_type=code&redirect_uri=http%3A%2F%2Fwww.pinzhi365.com%2Fconnect%2Fcallback.do&state=f0a0f313-310a-4c31-8365-2e903740445c

即redirect_uri解码后其实为 :http://www.pinzhi365.com/connect/callback.do ,但是对于QQ来说,QQ互联上明确说了,其回调域名并不是简单的/www.pinzhi365.com而是http://www.pinzhi365.com/connect/callback.do 这样一整串 — 》 具体规则可以参考我的文章《springsocial/oauth2—第三方登陆之QQ登陆4【redirect uri is illegal(100010)错误解决方式】》,当然也可以直接参考QQ互联官网。

但是这个项目在QQ上配置的redirect_uri为http://www.pinzhi365.com/qqLogin/callback.do,因此报redirect uri is illegal(100010)错误就很好理解了,其实大家可以试一下将QQ绑定拼接的url中的redirect_uri换成http://www.pinzhi365.com/qqLogin/callback.do是可以跳转到QQ授权页面的,但是这样QQ回调就无法回调到ConnectController里的方法了。

那该怎么解决这个问题呢?我觉得有如下两种方法:

  • 自己徒手写代码实现绑定的逻辑
  • 在QQ互联上多配置一个redirect_uri —》 下图是QQ互联上关于域名配置的介绍,可以看到redirect_uri 是可以配置多个的。


到这里不知道大家会不会想那微信为啥没有这个问题呢???
其实很简单,因为微信中让用户指定的不是完整的回调地址,而是域名因此无论是下面这种地址,

https://open.weixin.qq.com/connect/qrconnect?client_id=wxd99431bbff8305a0&response_type=code&redirect_uri=http%3A%2F%2Fwww.pinzhi365.com%2Fconnect%2Fweixin&state=ebb12821-f2bd-4903-b609-9403f429e2ac&appid=wxd99431bbff8305a0&scope=snsapi_login
还是
https://open.weixin.qq.com/connect/qrconnect?client_id=wxd99431bbff8305a0&response_type=code&redirect_uri=http%3A%2F%2Fwww.pinzhi365.com%2Fconnect1111111111111%2Fweixin&state=ebb12821-f2bd-4903-b609-9403f429e2ac&appid=wxd99431bbff8305a0&scope=snsapi_login
都是可以访问到微信的授权页面的。

5 解绑

微信解绑貌似比较简单,只需要发送一个url为/connect/{provideId}的delete请求就可以了,但是实际操作后发现还是有不少坑的。

5.1 在页面利用ajax发送delete请求 — 报ERR_TOO_MANY_REDIRECTS错误

我首先想到的是用ajax发送delete请求,于是写了如下页面:

<button onclick="deleteConnect()">微信解绑--无法完成</button><!-- 这样发的delete请求有问题--><script type="text/javascript">function deleteConnect() {console.log("1111111111111111")$.ajax({url: "/connect/weixin",type: "delete",success: function (res) {}})}</script>

但是发送请求后跟进到源码里发现会进入一个死循环,最后页面会报出如下错误:

5.2 利用restlet_client插件发送delete请求 — 302异常

利用restlet_client插件发送delete请求,跟进源码发现虽然不会进入死循环,但是会报302异常,效果如下:

5.3 使用form形式发送delete请求 — 终于进入解绑成功后的逻辑

其实5.1和5.2这两种方式都已经把userconnection表中的绑定信息给删掉了,但是就是无法跳转到我指定的解绑成功的视图(视图请看本文4.1),直到用了如下方式发送delete请求

 <form action="/connect/weixin" method="post"><input id="method" type="hidden" name="_method" value="delete"/><button>微信解绑</button></form>

简单展示一下解绑成功后页面的显示情况:

springsocial/oauth2---绑定和解绑处理【QQ绑定异常,微信解绑302/ERR_TOO_MANY_REDIRECTS】相关推荐

  1. java会员卡的绑定和解绑_前后端分离项目 — SpringSocial 绑定与解绑社交账号如微信、QQ...

    1.准备工作 申请QQ.微信相关AppId和AppSecret,这些大家自己到QQ互联和微信开发平台 去申请吧 还有java后台要引入相关的jar包,如下: org.springframework.s ...

  2. 7月个人:Windows和Linux绑定和解绑ARP 了解ARP命令的用途。 掌握ARP命令的使用。...

    Windows和Linux绑定和解绑ARP 了解ARP命令的用途. 掌握ARP命令的使用. ARP地址解析协议是一个重要的TCP/IP协议,可以用于确定对应ip地址的网卡物理地址.也可以使用人工方式输 ...

  3. 11-jQuery的事件绑定和解绑

    [转]11-jQuery的事件绑定和解绑 1.绑定事件 语法: bind(type,data,fn) 描述:为每一个匹配元素的特定事件(像click)绑定一个事件处理器函数. 参数解释: type ( ...

  4. jQuery的事件绑定和解绑

    1.绑定事件 语法: bind(type,data,fn) 描述:为每一个匹配元素的特定事件(像click)绑定一个事件处理器函数. 参数解释: type (String) : 事件类型 data ( ...

  5. 安卓案例:绑定和解绑服务

    安卓案例:绑定和解绑服务 一.运行效果 二.实现步骤 1.创建安卓应用BindUnbindService

  6. DPDK 网卡绑定和解绑

    参考: DPDK网卡绑定和解绑 DPDK的安装与绑定网卡 DPDK 网卡绑定和解绑 注意: 建议不要使用本文的eth0网卡绑定dpdk驱动. 1.进入DPDK目录: $ cd dpdk/tools/ ...

  7. android 绑定服务 解绑服务,安卓案例:绑定和解绑服务

    安卓案例:绑定和解绑服务 一.运行效果 二.实现步骤 1.创建安卓应用BindUnbindService 2.准备背景图片background.jpg,放到mipmap目录里 3.布局资源文件acti ...

  8. JS事件的绑定和解绑

    /* 事件三要素 1.事件源:在谁的身上绑定事件 2.事件类型:什么事件 3.事件处理函数:当行为发生的时候,执行那一个函数 ==>div.οnclick=function(){} ==> ...

  9. JS中事件的绑定和解绑

    JS中事件的绑定和解绑 一. 了解事件 1. 事件的三要素 2. 事件绑定分类 2-1. dem0级 事件 2-2. dom2级 事件 二. 事件的绑定 1. dom0级 事件 2. dom2级 事件 ...

最新文章

  1. Chrome浏览器禁止缓存
  2. 3D点云配准(二多幅点云配准)
  3. jbpm hibernate.cfg.xml 连接mysql配置_JBPM的jbpm.hibernate.cfg.xml有关配置
  4. 如何成为一名卓越的前端工程师
  5. 【Java基础总结】IO流
  6. 贺MSDN中文站开通测试!
  7. 物联网 终端设备_您拥有多少个物联网设备?
  8. 使用这些思路与技巧,我读懂了多个优秀的开源项目
  9. UPS Online Tools(一) --- Tracking
  10. ADO.NET学习笔记--索引Index
  11. 照相机成像原理 数码相机的成像原理
  12. android外接键盘打汉字,安卓手机外接键盘怎么切换输入法?
  13. Git报错remote: error: hook declined to update refs/heads/feature/XXX
  14. stm32 JTAG做普通io口(来自www.ourdev.com)
  15. Xman pwn level3 writeup
  16. 解决WPS每点击一下保存,就会出现tmp文件
  17. python手写汉字识别_用python实现手写数字识别
  18. 2010年F1大奖赛巴林揭幕战
  19. Redis【2022最新面试题】
  20. GBU1510-ASEMI电源专用15A整流桥GBU1510

热门文章

  1. 设计模式学习(四):Decorator
  2. 前端面试笔试题总结【持续更新~】
  3. 视觉设计师输出内容(IOS)
  4. 男人女人小孩共32人c语言,C语言:某工地需要搬运砖块,已知男人一人搬3块,女人一人搬2块,小孩2人搬一块.问用45人正好搬45块砖,有...
  5. 武汉!武汉!10 月 29 日!HDG 华为开发者汇来啦!
  6. 如何保持良好销售业绩
  7. LINUX重启MYSQL的命令
  8. unity内置图片查看器
  9. SCOI2016滚粗记
  10. Redis实战之微博关注功能