《微信小程序七日谈》- 第五天:你可能要在登录功能上花费大力气
《微信小程序七日谈》系列文章:
- 第一天:人生若只如初见;
- 第二天:你可能要抛弃原来的响应式开发思维;
- 第三天:玩转Page组件的生命周期;
- 第四天:页面路径最多五层?导航可以这么玩;
- 第五天:你可能要在登录功能上花费大力气;
- 第六天:小程序devtool隐藏的秘密;
- 第七天:不要捡了芝麻丢了西瓜
本系列的文章并非初学教程,而是笔者在具体开发过程中遇到的问题以及部分解决方案。
前几篇文章的内容主要集中于小程序开发框架中的一些机制细节,基本上都是客户端层面的知识。随着小程序项目的不断深入,我们不得不面对一些需要客户端与服务端协同完成的需求,比如用户登录功能。
大多数的小程序都会有自身的用户体系,然而小程序必须要经过微信账户的验证授权,然后再与第三方服务器(也就是公司自己的服务器)通信实现用户的登录。这里面就涉及到微信账户信息与自身用户信息的耦合。下面就简单介绍一下我们项目目前实现用户登录的技术细节。
浏览器环境下登录的实现方案
在制定具体实现方案之前,我们首先思考一下用户登录功能需要注意哪些细节。通常来讲,实现用户登录功能需要注意以下两点:
- 登录状态保存;
- 安全验证。
登录状态保存就是登录成功后请求站内数据接口时无需再次登录,客户端与服务器按照既定的规则进行用户有效性验证。浏览器环境下的登录状态保存通常使用cookie实现,这种方案的实现原理是浏览器发出的http请求header中会携带客户端的cookie。如下图:
安全验证是为了应对流量劫持,防止中间人攻击,在我们的页面中插入乱七八糟的内容。大家可能会想到使用https来防止流量劫持。https可以应对绝大部分的应用场景,有效的防止流量劫持。但其实市场上有一些“黑科技”软件可以捕获并且解密https加密信息的,比如Fiddler。所以在https的基础上,再进行一层安全验证是还有必要的。
大家可以参考这篇文章了解fiddler解密https的知识。
自定义安全验证通常的方案是客户端与服务器约定好一个验证签名,客户端在发出http请求之前按照约定好的算法计算出一个签名字符串,并且在http请求中将计算签名的参数传递给服务器。服务器接收到请求之后,解析出签名字符串和客户端传递的计算参数,然后按照同样的算法计算出签名字符串,并将其与客户端的签名进行对比,完全一致则验证通过,否则返回验证未通过的数据。如下图:
上述流程成立的前提条件是:
- 浏览器可以获取到UA信息;
- 浏览器发出的http请求header中会携带UA信息,服务器可以获取。
那么这套方案在小程序平台上是否可以复用呢?答案是否定的。
小程序的限制
目前我们所知的小程序存在以下限制:
- 不支持cookie,所以使用cookie储存登录状态的方案不可行;
- http请求header不携带设备信息,服务器无法获取。
但是我们在吐槽小程序重重限制的同时得到了一个好消息:http请求可以自定义header。我们仿佛看到了解决问题的银弹。
使用自定义header传递敏感信息
登录识别信息在无法使用cookie传递的限制下只有两种传输途径:
- url query
- http header
小程序提供了获取设备信息的API,提供了在客户端计算签名字符串的参数。按前文提到的验证规则,客户端计算签名的参数必须传递给服务器才能保证两端计算的一致性。所以我们又面临了之前的抉择,是query还是header?
其实使用任何一种途径都可以完成需求,但是url query(通常称为data
)的语义应该是与接口功能紧密相关的数据,并且http请求的header比url query数据更保密,所以我们团队最终采用header传递登录识别信息和设备信息的方案。
登录实现方案
确定信息的传递方式只是第一步,在小程序平台下实现自己的用户登录仍然有很多细节需要琢磨。我们首先看一下官方文档给出的第三方登录流程图:
官方文档给出的流程是实现第三方登录的基本流程,但是具体的登录功能中仍然有一些细节上的不同,比如:
- 手机验证码登录;
3rd_session
和openId
不能明文暴露给客户端,需要进行加密;- 登录状态保存的有效期;
- 用户登录的服务器与基础服务的服务器并不是同一台。
在小程序登录机制的基础上,我们团队在制定安全登录功能时最终采用了如下方案:
对比微信官方的登录流程图,有以下几个细节:
3rd_session
和openId
不直接暴露给客户端,而是通过可逆的加密算法进行加密后,组合成token
暴露给客户端;- 第一次请求基础功能服务器时并不验证签名,此次请求的目的是从微信服务器获取
3rd_session
和openId
并且加密后返回客户端,以便后续请求使用; - 第二次请求的目标是用户服务器,携带token的sign。用户服务器首先会进行签名验证和手机验证码校验;
- 验证通过后解析
token
获取3rd_session
和openId
,然后与uid结合重新计算token。最后将uid和token一并返回给客户端。
用户登录成功后,客户端将uid和token储存在本地,以便后续请求数据接口使用。
客户端储存token和uid的方式可以采用storage或者
app.globalData
。
数据接口的请求验证方案
数据接口是在用户登录成功之后才可以进行请求,相比较登录功能,数据接口的请求验证方案要简单很多。如下图:
接口请求只需验证sign以及token即可,如果token错误或者已过期,则返回客户端重新登录的标识。
总结
想在微信小程序中实现自己的用户体系需要花费一些力气,每个公司都有自身的用户登录验证体系,同时还要考虑安全性和状态保存问题。在小程序不支持cookie、http请求header不携带设备信息等限制下,实现登录功能的各种细节都需要采用一些折中的手段。而且往往这些方案显得有些臃肿并且难以维护,非常考验开发者的抽象能力。
本篇文章阐述的是笔者团队目前采用的登录实现方案,并非最佳实践,许多细节仍需打磨。希望可以给大家一些参考。