记一次授权登录系统的前端开发


前段时间公司业务扩展,需要做类似微信的 oauth 授权登录,整理了一些东西,记录一下。

需求

公司是做金融和电商这一块,金融监管加强,需要把金融和电商拆分成单独的两个业务,但是原来是使用一套账户体系,互相间有一些业务的耦合,类似于支付宝发淘宝优惠券的功能,所以拆分成两套账户体系也需要对两个平台的数据打通,由此我们引入了授权机制,将两个平台的账户进行绑定来打通数据。

授权方案采取的是类似于微信的授权方式,但还需要一个绑定的页面,用来手动绑定两个平台的账号。

对需求整理一下,大概的流程如下:

  1. 在 a 网站点击 b 网站业务的入口,先进入到 a 网站的授权页进行授权;
  2. 授权后进入 b 网站绑定页进行两个平台的账号绑定;
  3. 绑定成功后跳转到 b 网站的业务页面。

需求分析

对上面的需求进行分析,可以发现一共有 4 个页面:a 网站的入口页面、a 网站的授权页面、b 网站的授权页面和 b 网站的业务页面。

其中 a 网站的入口页面和 b 网站的业务页面不属于授权开发的页面,只有 a 网站的授权页面和 b 网站的授权页面和授权的具体流程有关,需要进行设计开发。

可以看到在授权的过程中,一共有三次链接跳转,分别是 跳转授权页、授权页跳转到绑定页、绑定页跳转到业务页。

程序设计

授权页借鉴微信的授权,通过参数 appidredirect_uri 获取授权的应用和授权后跳转的链接。但是授权后的绑定页没有借鉴,所以需要自己设计。

授权后 redirect_uri 会获取一个 code 拼在后面,跳转到绑定页后服务器可以用这个 code 来获取用户在 a 网站的相关信息,然后与用户输入的账号进行绑定,绑定成功获取 token 写入,然后就要跳到相应的业务页,业务页应该从 url 中带过来,我们把这个参数命名为 return_uri

确定方案后设计了一下授权流程,关于授权的流程如下:

  1. a 网站点击 b 网站的入口,进入到 a 网站的授权页,通过 appid 和登录状态判断该用户是否已经对该应用授权,若已授权,获取 code 拼接到 redirect_uri 并跳转;若未授权,则显示授权页;
  2. 用户点击授权按钮,获取 code 拼接到 redirect_uri 并跳转;
  3. redirect_uri 是 b 网站的账号绑定页,绑定页获取 code 发送给服务器,服务器判断该账号是否已经绑定过,若已经绑定过,则直接写入 token 进行登录并跳转到 return_uri 业务页面,若未绑定,显示绑定页;
  4. 在绑定页获取验证码,点击绑定按钮进行绑定,绑定成功写入 token 进行登录并跳转到 return_uri 业务页面,授权结束。

ps: 这只是一个简单的授权流程,对链接安全验证和错误处理等都省略了。

编码

入口的链接代码如下:

1
2
3
4
5
6
7
8
// 业务页
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 方法有问题,代码如下:

1
2
3
4
5
6
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 去掉就可以了,修改后的方法如下:

1
2
3
4
5
6
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: 如果需要直接解码的话也不要用 unescapeescapeunescape 在 ES3 后就弃用了,请使用 encodeURIComponentdecodeURIComponent 代替。

文章目錄
  1. 1. 需求
  2. 2. 需求分析
  3. 3. 程序设计
  4. 4. 编码
  5. 5. 遇到的坑
|