前段时间公司业务扩展,需要做类似微信的 oauth 授权登录,整理了一些东西,记录一下。
需求
公司是做金融和电商这一块,金融监管加强,需要把金融和电商拆分成单独的两个业务,但是原来是使用一套账户体系,互相间有一些业务的耦合,类似于支付宝发淘宝优惠券的功能,所以拆分成两套账户体系也需要对两个平台的数据打通,由此我们引入了授权机制,将两个平台的账户进行绑定来打通数据。
授权方案采取的是类似于微信的授权方式,但还需要一个绑定的页面,用来手动绑定两个平台的账号。
对需求整理一下,大概的流程如下:
- 在 a 网站点击 b 网站业务的入口,先进入到 a 网站的授权页进行授权;
- 授权后进入 b 网站绑定页进行两个平台的账号绑定;
- 绑定成功后跳转到 b 网站的业务页面。
需求分析
对上面的需求进行分析,可以发现一共有 4 个页面:a 网站的入口页面、a 网站的授权页面、b 网站的授权页面和 b 网站的业务页面。
其中 a 网站的入口页面和 b 网站的业务页面不属于授权开发的页面,只有 a 网站的授权页面和 b 网站的授权页面和授权的具体流程有关,需要进行设计开发。
可以看到在授权的过程中,一共有三次链接跳转,分别是 跳转授权页、授权页跳转到绑定页、绑定页跳转到业务页。
程序设计
授权页借鉴微信的授权,通过参数 appid
和 redirect_uri
获取授权的应用和授权后跳转的链接。但是授权后的绑定页没有借鉴,所以需要自己设计。
授权后 redirect_uri
会获取一个 code
拼在后面,跳转到绑定页后服务器可以用这个 code
来获取用户在 a 网站的相关信息,然后与用户输入的账号进行绑定,绑定成功获取 token
写入,然后就要跳到相应的业务页,业务页应该从 url 中带过来,我们把这个参数命名为 return_uri
。
确定方案后设计了一下授权流程,关于授权的流程如下:
- a 网站点击 b 网站的入口,进入到 a 网站的授权页,通过 appid 和登录状态判断该用户是否已经对该应用授权,若已授权,获取
code
拼接到redirect_uri
并跳转;若未授权,则显示授权页; - 用户点击授权按钮,获取
code
拼接到redirect_uri
并跳转; redirect_uri
是 b 网站的账号绑定页,绑定页获取code
发送给服务器,服务器判断该账号是否已经绑定过,若已经绑定过,则直接写入token
进行登录并跳转到return_uri
业务页面,若未绑定,显示绑定页;- 在绑定页获取验证码,点击绑定按钮进行绑定,绑定成功写入
token
进行登录并跳转到return_uri
业务页面,授权结束。
ps: 这只是一个简单的授权流程,对链接安全验证和错误处理等都省略了。
编码
入口的链接代码如下:
// 业务页 const return_uri = encodeURIComponent('https://b.com?a=b&c=d'); // 绑定页 const redirect_uri = encodeURIComponent( `https://b.com/bind?return_uri=${return_uri}` ); // 授权页 const authorized_uri = `https://a.com/authorized/?appid=123&redirect_uri=${redirect_uri}`;
最后得到的放在 a 页面的入口链接为:
https://a.com/authorized/?appid=123&redirect_uri=https%3A%2F%2Fb.com%2Fbind%3Freturn_uri%3Dhttps%253A%252F%252Fb.com%253Fa%253Db%2526c%253Dd
这样在 a 网站的授权页对 redirect_uri
进行 decodeURIComponent
解码,并将授权获取到的 code
拼接得到 b 网站的绑定页地址:
https://b.com/bind?return_uri=https%3A%2F%2Fb.com%3Fa%3Db%26c%3Dd&code=xxx
在 b 网站的绑定页对 return_uri
进行 decodeURIComponent
解码,绑定后写入 token
就可以跳转进入业务页了。
遇到的坑
在 b 网站的绑定页获取 return_uri
后发现参数丢失了,https://b.com?a=b&c=d
,只能获取到 https://b.com?a=b
,后面的参数都丢失了,分析发现是获取 url params 的 getQueryString
方法有问题,代码如下:
function getQueryString(name) { var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)'); var r = window.location.search.substr(1).match(reg); if (r != null) return unescape(r[2]); return null; }
这是网上流传比较广的获取 url params 的方法,我们一直也是用的这个方法,但是这个方法最后获取到 params 后会调用 unescape
处理后再返回,所以对链接多解码了一遍,跳转绑定页时就把参数丢失了,后来把 unescape
去掉就可以了,修改后的方法如下:
function getQueryString(name) { var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)'); var r = window.location.search.substr(1).match(reg); if (r != null) return r[2]; return null; }
ps: 如果需要直接解码的话也不要用 unescape
,escape
和 unescape
在 ES3 后就弃用了,请使用 encodeURIComponent
和 decodeURIComponent
代替。