2、步骤17 显示应用名称和请求授权信息:因为微信支持很多的第三方应用,需要明确告知用户正在登录哪个应用,应用可以访问自己的什么信息,这都是用户做出登录决定的必要信息。因此扫码之后,微信手机端就需要去微信开放平台查询下二维码对应的第三方应用信息。
3、步骤24 登录临时授权Code:微信开放平台没有直接向浏览器返回登录用户的信息,这是因为第三方应用还需要对用户进行授权并保持会话的状态,这适合在应用的服务端来处理;而且直接返回用户信息到浏览器也是不安全的,并不能保证二维码登录请求就是通过指定的第三方应用发起的。第三方应用会在步骤27中携带这个授权Code,加上应用的AppId和AppSecret,再去向微信开放平台发起登录请求,临时授权Code只能使用1次,存下来也不能再用,且只能用在指定的应用(即绑定了AppId),AppSecret是应用从服务端提取的,用来验证应用的身份,这些措施保证了微信授权登录的安全性。不过验证通过后还是没有直接返回用户信息,而是返回了一个access token,应用可以使用这个token再去请求获取用户信息的接口,这是因为开放平台提供了很多接口,访问这些接口都需要有授权才行,所以发放了一个access token给第三方应用,这种授权登录方式叫做OAuth 2.0。基于安全方面的考虑,access token的有效期比较短,开放平台一般还会发放一个refresh token,access token过期之后,第三方应用可以拿着这个refresh token再去换一个新的access token,如果refresh token也过期了或者用户取消了授权,则不能获取到新的access token,第三方应用此时应该注销用户的登录。这些token都不能泄漏,所以需要保存在第三方应用的服务端。
这里有一个有意思的问题:为什么微信的二维码登录页面Url中没有第三方应用的签名?
以极客时间的这个微信登录二维码页面的Url为例:
其中appid是微信开放平台分配给极客时间的应用Id;redirect_uri是用户授权登录后微信回调的极客时间url,虽然看起来很长,其实就是在极客时间内的页面之间跳来跳去;state是极客时间生成的一个登录id,不同的state会生成不同的二维码,微信回调极客时间的时候会带着这个state。
这个url中只有极客时间的appid,没有极客时间的签名,也就是说微信会生成极客时间的登录二维码,但是不验证这个二维码登录请求是不是极客时间发起的,那么任何人都可以生成极客时间的微信登录二维码页面。这好像不太严谨。
这样安全吗?攻击者可以很简单的获取某个应用的微信登录二维码,然后再骗取用户的登录授权(这个也相对简单,弄个假冒的网站,或者直接话术忽悠,用户很可能不仔细看扫码确认的内容),再通过浏览器拦截或者DNS攻击获取到临时授权Code,直接越过检查应用State,前边这些都相对容易。但是向微信验证临时授权Code的时候必须携带App Secret,这个就很难了,除非第三方应用开发者自己泄漏了这一核心机密。对于用户信息的保护,整体上来说是安全的,攻击者基本上拿不到访问用户信息的access token。如果在url中加上这个签名,对用户信息的保护作用也没有加强,授权Code还是很容易获取;而且签名一般也是依赖App Secret,如果泄漏了App Secret,签名也就失去了意义。
如果有人不断地生成某个应用的微信登录二维码怎么办?这是其他类型的攻击了,微信可以采取一些限流措施,甚至直接封掉某些IP,这对正常用户没什么影响。
微信二维码登录的变种微信除了跳转到二维码登录页面的方式,还提供了第三方网站中内嵌二维码的方式。通过微信JS SDK生成二维码并在网页的指定区域展现,用户扫码同意登录后,微信JS SDK发起重定向或者在iframe中打开应用回调页面,传递临时授权Code和应用State,后续的流程就都一样了。
另外这里的第三方应用如果是移动端App,微信开放平台也支持扫码登录的方式,区别在于需要集成微信SDK,获取二维码和接收用户授权Code都是通过SDK回调的方式,后续的流程也都一样。