那一年,我所在公司的用户量达到了公司成立以来的新高峰,经过多个程序员日日夜夜加班,每个业务系统达到了几乎四个 9 的稳定性,同时业务在业界也有了一定的知名度。
PS:以下业务场景只针对于 Web 系统,而且 Web 页面有后台服务程序的场景。
那一天突然有一个合作商登门拜访,提出合作共赢的意向。业务的场景就是我们的系统用户能够在他们系统登录,并能够获取用户一定的信息以便进行一些业务操作。
他们希望我们能够把已存在的用户数据 Copy 一份导入他们的系统,并且新注册的用户进行单项同步更新。这不是虾扯蛋吗?.....
为什么不可行
为了实现用户信息互通而达到业务要求,其实方案有很多。如果不是底线情况下,同步用户信息这种方案就是一个外行人,一个扯淡的方案。为什么这么说?
首先说信息同步这种方式,如果是单项同步,双方所有相关人员的工作量已经非常之大,一定条件下单项同步升级为双向信息同步,双方的编程人员将会苦不堪言。
另外撇开工作量,用户的信息本质上属于用户的私密信息,一个用户能够把自己的隐私放心的存储在你这里,就说明了对公司的信任度。
一旦发生用户信息复制的操作,本质上是对用户的不负责任,道德上,法律上都有所欠缺。
解决方案
作为一个技术人员,排除不合理方案,提供在业务可行情况下的技术方案是职责所在,那有没有不用复制用户信息这么 low B 的方案呢?
假设我们所在公司的系统为 A,业务的域名为 www.A.com,第三方系统为 B,业务域名为 www.B.com。
记住我们的最终业务目标:允许我们公司的用户(A 系统)在第三方系统(B 系统)能够登录,并且能够获取用户一些相关的信息。极限业务情况下,在 A 系统用户修改了相关信息,并且同步到 B 系统。
解决方案 1
在第三方系统登录的入口,允许我方用户输入账号密码,然后第三方系统(客户端或者服务端都可以)携带用户输入的账号密码请求我司登录服务器。
如果验证通过则返回用户相关信息,第三方系统接收到返回数据,按照自己相关的登录流程进行登录,并且可以存储用户相关的信息。
请求的形式和大体的流程如下图所示:
- http://www.A.com/login?loginname=caicai&pwd=buzhidao
说实话,我并不推荐这种方案,虽然它比直接复制用户信息要好一些,但是依然问题很大,用户在无形中已经把账号密码或者其他登录凭证泄露给并不信任的第三方系统中,而这可能并非用户想要的结果。
解决方案 2
以上方案有一个致命的缺点,那就是登录页面是用户并不信任的第三方页面,如果能避免这样的危险,让用户在信任的我方登录,会大大增强用户的信任度。
技术方面在我方实现登录实在是容易,唯一需要考虑的是用户登录成功之后如何把用户信息发送给第三方系统。
如果采用请求调用的方式(比如:登录成功,我方调用第三方一个接口),技术上可以实现。
但是下次再来一个第三方申请这样的业务,我方的调用接口可能会需要修改,所以现在业界比较好的也比较通用的方式是通过地址的跳转来实现。
具体流程如下:
- 用户在第三方点击登录,跳转到我方提供的登录页面,页面 URL 中带有登录成功跳转的页面地址,并在此页面输入账号密码。
- 我方根据用户账号密码判断用户正确性,登陆成功,获取用户信息。
- 然后跳转到第三方提供的登录成功跳转页面,并把用户信息携带过去。
- 第三方跳转页面接收到用户信息,处理剩余业务,流程结束。
第一步中第三方跳转到我方的登录页面 URL 如下所示:
- http://www.A.com/login?type=userinfo&redirecturi=http://www.B.com/callback
解决方案 3
方案 2 中登录部分已经和方案 1 有了本质的区别,虽然仅仅是一个登录方的改变,安全性以及对用户隐私的保护上却有着大大的提升。
但是流程中却依然存在着主动传输用户信息,如果有人劫持的话,还是有用户信息泄露的风险。如何避免这样的风险呢?
试想,能否利用其他凭据来代替用户信息呢?当然是可以,这也是现代 Web 系统实现授权的普遍方式。
用户信息取而代之的是一个令牌,而且这个令牌有一定的时效性,只能维持一段时间内有效,这在一定程度上保护了系统数据。
第三方系统获取到这个令牌之后,每次获取用户信息都会携带着这个令牌作为凭证,我方的系统同时也只认可这个令牌作为授权的凭证。
- http://www.A.com/login?type=token&redirecturi=http://www.B.com/callback
这里我要顺便说一下,令牌的下发是通过前端(浏览器)的跳转传输给第三方系统,然后第三方系统的前端传输给后端,然后第三方的后端携带令牌获取用户信息。
要注意哦,如果是第三方前端页面携带令牌去获取用户信息,毫无安全性而言。
解决方案 4
方案 3 其实在很多时候已经足够了,但是有一点需要注意,每个令牌有一定的有效时间,这是设计上的优势,同时也意味着令牌如果被其他人获取到,一样可以窃取用户信息。
由于在方案 3 中令牌的下发实际上还是通过前端(浏览器)来传输的,凡是在前端传输的情况下,就会有泄露的风险,那有没有办法避免在前端传输呢?
这里需要提醒一点,要想实现我方用户可以登录第三方系统,并且在保护用户隐私的情况下,在我方登录是必须的。
而且我方系统必须颁发给第三方系统一个凭证才能达到第三方获取我方用户的要求。
既然传输凭证不可避免,于是人们便想到了可以在前端(浏览器)传输一个只有一次有效的凭证,然后第三方后端依据这个凭证去获取令牌,因为服务端的通信要比前端(浏览器)的通信要安全的多。
于是方案 4 应运而生:
- 用户跳转到我方登录页面进行登录。
- 我方验证用户用户名密码无误,产生一个有效次数为 1 并且一定时间内有效的 Code,并携带着这个 Code 跳转到第三方的回调页面。
- 第三方回调页面,收到 Code 参数,传输给后端程序。
- 第三方后端程序收到 Code 参数,携带着 Code 调用我方接口。
- 我方验证 Code 有效性,如果有效则返回令牌信息。
- 第三方收到令牌信息,携带令牌信息调用我方接口获取用户信息。
- 我方验证 Token 有效性,如果有效则返回用户信息。
- 之后的每次调用都携带着 Token 进行访问,Code 就算被人获取到已经不起作用。
- http://www.A.com/login?type=code&redirecturi=http://www.B.com/callback
升级方案
方案 4 虽然看上去已经足够好,但是并非完美。主要表现在如下几点:
①当第三方跳转到我方登录页面的时候,我方并不知道这个第三方是谁,是不是可信任的,所以有必要让我方识别这个第三方是否可以信任。
我方在授权第三方的时候可以给每一个第三方颁发一个类似于 appid 和 appkey 的数据,appid 用来标识每一个我方授权的第三方,而且每一个 appid 必须注册进行回调的 URL。
这样当第三方跳转到我方登录页面的时候,我方就可以识别出来这个第三方以及回调跳转的 URL 是否有效。
②当第三方携带着 Code 去换取 Token,以及之后携带 Token 去获取用户信息的每次通信,都应该按照我方规则利用 appid 和 appkey 进行签名处理,这样我方的服务器端也能够识别出来调用方是否是可信任的。
③在用户登录授权的页面,用户可勾选自己授权给第三方的数据内容,这些权限将作用于 Code 以及令牌中。
④由于每个令牌都有失效时间,如何更新令牌则会是一个技术点,其实完全可以在下发令牌的同时也下发一个用于更新令牌的令牌,这个令牌随着每次重新下发令牌而更新。
⑤我方用户的信息每次更新的时候,可以把相关的令牌失效,以达到让第三方重新获取用户信息而同步的效果。
⑥我方登录页面以及供第三方调用的所有接口都应该采用 HTTPS 协议,并要求所有的第三方回调页面必须也全部采用 HTTPS,这能有效的防止恶性劫持。
不知道我把不清楚 author2.0 授权的同学教会了没有,如果还不清楚,请底部留言交流。