微信小程序 webview-微信小程序调用微信支付
简介
详细讨论如何通过迭代演进,最终实现一个以微信小程序作为认证媒介的 OAuth 2 验证流程,在保证安全的前提下,为网页提供获取系统用户信息的能力。
本文最终探讨了如何将微信小程序作为系统的 OAuth 认证媒介,而不是讲述如何在微信小程序中对接微信登录。
前提
整篇讨论都限于通过企业认证的微信小程序,个人版的微信小程序不适用!
企业的微信小程序和微信的对接已经完成。
背景
企业基于微信小程序开发了完整的登录注册、用户档案等功能。随后,企业的运营方不断地在营销活动中复用微信小程序的登录注册和用户信息获取的能力,这种营销活动页面,通常外包给第三方开发,以 webview 的形式嵌入在微信小程序中,本质上是一个 html 网页。
复用价值节省开发成本
这种活动页面成百上千,由不同的外包服务商独立开发,每次都要独立开发登录注册的话,成本太大。从 DRY 原则上说,这些活动页面只需要关注每次活动的具体变化的内容即可(通常是一个不同的网页小游戏)。
提升用户体验
登录注册时,要求用户提供手机号,这十分普遍。但是,在网页中实现这个功能,一般都是通过用户填写手机号,接收验证码,再填入验证码由服务器验证。这个过程比较耗时,需要用户的多次输入。相反,微信小程序可以利用微信的能力,只需要用户点击一次授权按钮,即可获得手机号。
通过重用微信小程序的用户登录注册功能,用户感受更方便快捷。
需求分析
这些活动页面,最终需要的用户信息,多数情况下,只有一个,那就是用户在系统中的唯一标识。这样,在随后的互动中,就可以区分不同的用户来。比如,在游戏互动中,用户中奖了,就需要记录是谁中了什么奖。因为这些互动方式都是第三方开发的微信小程序 webview,由他们来存储这些中奖信息很自然。但最终要关联到内部系统的用户,就需要这个用户身份标识。
所以,终极问题是,微信小程序的 webview 里的网页,如何获取到微信小程序的用户唯一身份标识?
方案零(客户端明文传输用户身份标识,可以立即否决)
在 webview 里的互动进行到某个时机,网页调起微信小程序的登录注册功能,用户完成登录注册之后,微信小程序通过 url 回调网页,在 query string 中,带上 userId。
安全性
不安全!
开发周期
最短
修改范围解决了什么?
webview 中的网页获取不到 userId 的问题
不能解决什么?
没有安全性可言。如果通过伪造 userId 直接调用网页的 url,在该网页的域中,用户身份信息就被伪造了。
补救措施
一旦发现这种行为,网页端能做的是改版,不展示任何用户信息;但是数据库里存放的错误映射关系,没有办法恢复了,不能再被使用。
流程
结论
不能使用!
方案一(jwt,不推荐)
由于前端 url 的传递参数容易修改,所以需要避开使用前端在不同系统间直接明文传递 userId 的方式。一个很自然的想法就是使用 jwt,将 userId 包装在 jwt 里,由小程序传给 webview。网页端接收到 jwt 后,需要验证 jwt 是否有效。由于 jwt 一旦被篡改,就通不过校验,从而可以防止任意伪造 userId 的情况。
安全性
较不安全
开发周期
很短
修改范围解决了什么
方案零的安全问题。在方案零中,任何人都可以通过猜测 userId,遍历所有网页端保存过的用户信息。采用这个方案,猜测的 userId 会通不过网页端后端的验证。
不能解决什么
被捕获的 jwt,在有效期内,可以被重放攻击。用户 Bob 仍然可以通过拿到用户 Alice 的 jwt,打开网页 url,看到 Alice 的信息。
补救措施
同方案零
流程
前提
这个方案由于使用了 jwt,并且要求网页服务器端做验证,所以需要微信小程序后端和网页后端共享颁发 jwt 的密钥。
结论
不推荐!
因为很难把控第三方开发们,一定做了验证操作。而且,要共享密钥,这不可行。
方案二(OAuth 2 客户端凭据许可模式)
由于方案零和方案一,本质上都是通过客户端传递 userId,只是一个通过明文,一个通过 jwt。综上所述,这都不安全微信小程序 webview,所以可以再进一步,去掉客户端交流用户信息的渠道,而改为服务器端来进行 userId 的查询。即让网页端的服务器端来向小程序服务器端查询 userId,但这要求网页服务器端在查询时带上用户的另一信息。考虑到都是在微信生态,可以让网页服务器端查询系统的 userId 时,带上unionId。
安全性
较安全
开发周期
较短
修改范围解决了什么
由于 url 上不用带上明文或者加密的 userId,用户 Bob 没有机会伪造用户 Alice 的身份,更不能通过猜测 userId 遍历系统用户了。
不能解决什么
一旦网页端服务器获取到了服务器端信任的令牌,就可以从微信小程序后端查询任意用户的 userId(不需要用户授权)。所以这个方案并不能防止网页端服务器来遍历会员信息。
补救措施
一旦发现网页端本身存在不安全的行为,可以立即吊销用于建立服务器端信任的 client id 和 secret,使得网页端服务器不能再使用 userId 的查询接口。
流程
网页授权拿到了用户的 unionId
网页服务器端用 unionId,向小程序服务器查询userId (server to server talk 的方式)
查到直接做后续操作
没查到拉起小程序登录注册,然后小程序回调网页(webview 方式)
网页的逻辑回到 2,再次查询。得到 userId,进行后续操作
前提
网页端使用和小程序在相同的开发平台里绑定到同一公众号主体的账号,授权获取 unionid。
结论
在工期紧急的情况下,推荐使用
方案三(OAuth 2 授权码许可模式)
方案二要求系统信任网页端,从而开放 userId 查询权限给到网页端。注意当把用户的 userId 给到网页端时,不需要经过用户的同意,因为采用了客户端凭据许可模式。
本方案更进一步,网页端要获取用户的 userId,需要经过用户显式同意,即采用 OAuth 2 的授权码许可模式。
安全性
更安全
开发周期
最长
修改范围解决了什么
这个方案解决了方案二不能防止网页端遍历系统用户的问题,因为只有用户显式授权,它才能拿到用户信息。
不能解决什么
一旦用户同意,userId 就“泄露”给了网页端。这个网页应用自己保存的(userId, 中奖信息)这个映射关系,由这个网页应用自己负责保证安全。
补救措施
如果发现网页端应用本身存在不安全的行为,可以吊销其 clientid/secret,使得该网页端不能再访问微信小程序后端接口。
流程
1. 网页端的已有流程(方案二中的网页端要求用户授权,拿用户的 unionid,这一步可以去掉)
2. 网页端拉起小程序,用户登录/注册和小程序自己内部的登录/注册要多一步,即展示一个页面,明确告知用户:该网页应用想使用你的凭据,以获取你的用户信息。措词可以相应修改
3. 用户拒绝登录/注册,流程结束
4. 用户如果同意,在小程序完成了登录注册流程,那么小程序回调webview,在 url query string 里带上一个后端颁发的临时 code,有效期 5 分钟(防止重放,有效期必须很短,而且限制只能被使用一次)
5. 网页端服务器向小程序后端请求授权(client_credential)
6. 授权失败,流程结束
7. 授权成功,使用server trust access_token 作为凭据,用 code 换取用户的 access_token,scope: userinfo
8. 换取失败,流程结束
9. 换取成功,使用该用户 access_token,向小程序后端查询用户信息(userId 等等)
结论
在时间允许的情况下,最推荐使用这个方案!
总结
本文列举了在webview中复用微信小程序的登录注册功能以换取用户信息的 4 种方案,今天就写到这里。如果有考虑不周的地方,后面再发文修补,再有时间甚至可以分享关键代码,或者做一个开源代码库。