今天和同事讨论一个老生常谈的问题:如何防止请求参数被篡改。这个问题应该很老了,不管是C/S或B/S模式,诞生之初就应该已经面临这个问题了。

写过支付宝或微信支付对接的同学肯定对解决方案不陌生,那就是【url签名】。我们只需要将原本发送给server端的明文参数做一下签名,然后在server端用相同的算法再做一次签名,
对比两次签名就可以确保对应明文的参数没有被中间人篡改过。

没错,很简单不是么,一般大家都选择一种不可逆算法来签名,例如MD5。但问题来了,如果签名的时候没有一个“私钥”的存在,中间人也可以自己重新签名来达到篡改的目的啊。
等等,那支付宝和微信的签名怎么做的?别忘了人家是提供了私钥的哟!回忆一下相关文档就真相大白了。

那既然私钥很重要,那就加上呗。但最最最困难的问题诞生了,如何在前后端通信的基础上传递这个私钥呢?私钥总是需要双方都知晓的不是么?支付宝和微信之所以需要考虑这个问题,是因为
它们的api是直接对接服务端的,代码不会跑在浏览器端,所以是非常安全的。

假如中间人从最早就存在,那你无论如何你都不可能传递一个私钥给对端,那HTTPS是怎么做到的?相信你查了一圈资料后也会知道它的安全也是有前提的。总之,我们得找一种方法传递一个私钥到对端。

HTTPS借助非对称加密算法来实现,前提是通信两端确实创建了一个连接,哪怕这个连接是处在不安全信道上的。但如果客户端从来都没有直连服务端,那神仙也帮不了你。所以还是要保证自己的网络环境的安全,公共wifi下还是不要上淘宝了。
但你依然还是要保证通信要基于https,因为绝大多数情况下HTTPS提供的保障都是足够的。

我们接下来讨论一下一个小众的场景,看看在一些特殊场景下是否可以更好的来完成防止中间人篡改的问题,我们就假如现在要从客户端提交一个请求来告诉服务端说“你很爱你的老婆王铁柱”。
为了避免你的情敌在中间给你篡改信息,我们的系统要怎么做?

你一定用过手机网银app对吧?你有没有记得你每次登录系统后,会有个弹窗欢迎语:欢迎登录,吴彦祖。(对,没错,我真名就是吴彦祖)
这个名字是你之前就设置好的,系统每次你登录的时候返回给你这个提示,就是确保没有中间人攻击的。简单,但非常有效,因为用户可以几乎没有额外的操作就能判断出是否存在风险。
这是一种值得借鉴的方案,它用来解决 在连接创建之初通信环境是安全的。(当然,中间人也是可以代理来返回的)

接下来,依然可能突然在通信中间出现了中间人,毕竟http请求是独立的。上面提到了,不要靠https了,那我们就得想办法创建一个独立的通信渠道来完成秘钥的传递。

不考虑运营成本的话,短信不错,当客户端提交请求时,会收到一条短信验证码,客户端利用验证码做秘钥进行签名即可,但短信是要钱的!
你是否听说过google-authenticator,就好像早先的将军令,一种基于时间片的算法。我们也可以利用它生成的code来做签名秘钥,完美!

最后,为了保证完全没问题,我们还可以增加一个让用户确认的页面,在页面上显示服务器端收到的信息内容。(当然,中间人也是可以修改的)

我们做了这么多,除了短信和google-authenticator两步外,其它步骤都仅仅是增加了中间人攻击的门槛而已。但核心的这两步却对用户使用体验伤害比较大,使用的时候需要斟酌。

不说了,再考虑一下吧~

补充:
针对https安全提升方案里,可以看看 https://imququ.com/post/http-public-key-pinning.html