1.APP后端开发系列:登陆系统设计中的注意问题

来源:互联网 发布:手机上传淘宝主图视频 编辑:程序博客网 时间:2024/06/06 07:44

github博客-首发地址

想写这个系列很久了,因为之前做这个东西花费了大量的精力,有必要分享出来与大家共享。以前也写了一些关于 APP后端开发的系列文章 由于当初功力不够,很多问题描述不清楚或者解决方案过于复杂、不严谨等。

这一次查了很多资料,问了很多相关人士。准备再结合自己实际工作中的问题再次进行一些补充。就先从登陆的设计开始吧!

越想越糊涂

之前再做这一部分的时候,总想着复杂的技术,说出去多屌炸天呀。一般来说登陆的流程是:

image

当时对于安全性过度痴迷,确走偏了道路。首先提交的时候怕信息被人劫持,因此客户端在上传时,进行AES加密,服务端解密出结果。服务端返回的信息也会AES加密,然后客户端解密。

然后这里又带来另外一个问题:加密信息放在了客户端,那么一但客户端被反编译,hacker拿到秘钥,那么对于服务端来说加密就没有任何意义了。又为了不在客户端保存这么敏感的信息,就像秘钥由服务端下发。这样子服务端可随时对秘钥进行变更。

到这里又带来了一个新的问题,感脚一切又回到了起点:下发秘钥要走http,那么依然可能被人劫持。这时候该是加密还是怎么弄呢?如果加密,客户端又放了一个秘钥过去。那这个秘钥依然可能被人反编译。不能再从服务端获取这个秘钥吧?为了确保上个秘钥的安全,生产下一个秘钥……

当时就是陷入了这样的死循环,不可自拔。现在想想真是too young too simple!

简单、有效

首先在这里还是先说一下:如果你的产品刚刚起步,不要过于纠结性能、安全

先说性能:你的产品才推出的时候,冷启动的用户数一般来说不会超过1000人(这已经是很不得了的冷启动人数了)。然后你的并发也不会超过100。这种级别的访问,相信机器硬件就可以帮助你解决。如果你的条件远远超出以上规模,那么你的实力绝对足以应付即将发生的事情。
谈谈安全:安全这个事情,从一开始就要考虑,但是不能过于纠结(我之后可以讲讲我在做短信验证码这一部分的遇到的一个经历)。过早介入,会导致系统开发速度降低,过早做了一些不需要的事情(hacker来搞你也是需要成本的,在你没价值的时候,没人愿意来搞你)。所以早期应该重视开发成本,抓紧时间,早日上线。

另外,安全与性能有时候也是鱼与熊掌。

演化之路

这一部分会有一些代码与图来进行说明。在安全方面逐步演进。前面说前期开发只要快就好,但是这里也要注意一个问题,就是后续升级能够弥补前面的错,要给未来升级留下余地。因为否则你的系统始终留下了一个隐患。

实现功能就好

这是最开始的阶段,重点考虑功能实现。用户提交username + pwd 服务端验证通过后,返回一个令牌token。

这里需要注意的几个部分是要为未来的升级做好准备。我经常遇到的几个初期设计是:

  1. 验证通过后,把用户uid+username+salt等md5后,作为token返回到客户端。
  2. 对token加入时间戳,过期后客户端重新提交username + pwd验证后再发一个token到客户端
  3. 服务端生成一个token后下发到客户端,客户端按照约定的规则加密后请求服务端。

先说第一种带来的问题:生成的token永久不变,那么别人获取到一个token就可以无限制的进行请求。直到你关闭了这个接口为止。为后续安全设计增加了成本。

第二种问题就有点老火了,虽然看似token只在一定时间范围内有效了,但是其实更不安全了。首先客户端需要保存用户的用户名与密码,如果用户手机平时不注重安全,很容易被人窃取。

第三种设计方案,这是我原先干过的一件事,是这三种方案中最垃圾的设计。得出的教训就是:绝不能把任何加密的事情交给客户端。这样子灵活性大打折扣。举例:还是升级接口了,现在本来token生成只是服务端的事情,服务端随时可动态改变规则,现在由于客户端也参与进来了,这事儿就麻烦了,你一改,客户端也要跟着改。没有任何灵活性可言。切记:客户端就接收,然后转发回服务端就好了。别再客户端进行加密!!!

经过这些坑的历练,参考oauth2.0,我现在采用以下方案:

用户提交username + pwd后,服务端返回以下信息:

{    "access_token":"2YotnFZFEjr1zCsicMWpAA",    "expires_in":3600,    "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"}

access_token 是用来进行访问的接口的,expires_in 是他的过期时间,到达过期时间后,需要用 refresh_token 来请求服务端刷新 access_token

这里几个重点是:refresh_token 仅能使用一次,使用一次后,将被废弃。另外这个 access_token 只在 expires_in 有效期内有效。

注意: 这里的 expires_in 仅返回秒数就好了。别返回时间戳。因为各个平台计算s的时间戳,不一致,这样子做更方便处理。

访问频率控制

上面我们简单实现了功能,现在app的流量上来了,有些功能也很复杂,如果某个接口访问量太大,会导致服务器崩溃,需要分别对每个接口每次访问设置频率(也可以统一设置每个接口访问的频率)。

一般我的做法是加入一个中间件。每一个接口的访问频率做好一个对应的配置文件。比如:
* a接口 5s内可访问1次
* b接口 10s内可访问1次(可能非常耗时,如果同时过多请求会导致服务器崩溃)

那么就把 access_token 与这些关联起来。这里需要用到redis。当用户A进来访问了 a接口 那么设置这个token 5s内不能再次访问。

    if ($redis->get($key)) {        // 无法访问,还未到时间        return ;    }    // 设置频率控制key    $redis->setex($key, $expires, $value);    // 访问接口

这里需要考虑几个问题:

设置的访问时间要合理。举例:客户端一般启动的时候会请求多个接口,那么当这些请求到达后,服务端可能拒绝其中一部分访问(因为在频率控制内)

一般来说不需要对所有的接口都进行频率控制,仅仅针对重要的内容以及性能上有要求的接口进行频率控制。

账号安全考虑

现在又进一步了,需要考虑用户账号安全的问题。比如:QQ,有时候会提醒我们你的账号在香港登陆了。如果不是自己所为,赶快修改密码之类的。

实现这个功能,你需要记录每次登陆、启动时每个token对应的ip地址。如果ip地址与上次的ip不在同一个范围(这个规则由自己定,因为有的运营商ip经常变化,比如:长城)。就提醒用户是不是他自己所为,如果不是,就赶快修改密码。

现在很多app在开发之初,都是可以多个设备同时登陆。这样带来的安全问题也很多。如果要做成单个设备登陆,需要每个token对应一个deviceToken。

这一部分就不继续深入讨论下去了。

防DNS劫持

安全工作做得再好,如果有人能够获得大量合法用户的token,来请求你的借口,你也无法识别,因为从行为来看,这一切都是合法用户再进行。

以前为了防止别人获取到合法的信息,我才弄出了很狗血的客户端加密方法。导致后期升级的时候,诸多问题。这个东西其实很简单,使用https来进行请求(可以个人关键接口使用)

token

才开始做app服务端的时候,总想着token的设计。怎么才能生成一个好的token呢?现在想想真不知道当初怎么想的。

token的生成

首先搞明白这个token的作用就是一个令牌,用来标记一个用户的身份。那么首先他要唯一。其次他从客户端上传后,服务端能够验证这个token是由服务端生成的。

所以token生成只要满足以上目的,你随意就好了。当然别把敏感信息暴露出去了。

常用的一种生成方式:

  1. 该用户的uid,如:8888
  2. 该用户的口令,如: 123123
  3. 用户对应的salt,如:abcd
  4. 过期时间戳,如:1468293948

把上面几部分拼接起来:888:123123:abcd:1468293948

token = md5(‘888:123123:abcd:1468293948’);

token的验证

对于token也有两种方法进行验证。一是:服务端生成后,将token保存起来(redis或者mysql中)。客户端穿上来之后,检查是否有该token,如果有取出对应的信息,比如uid,验证是否匹配。

另一种方法是:根据上传的uid,生成对应的token,然后进行比较token结果是否一致(要保障该算法如果给定的值一定,结果必须唯一。常用md5)。

对于个人而言更倾向于第二种方案。第一种方案效率更高(可使用redis存储这个token),但是如果redis一但雪崩,就会造成所有用户登录失效,一定时间内不可登陆。初期越简单、越可靠更好。

总结

这一部分没有太多代码,主要是思路。还有涉及到H5的登陆问题也没有说到。下篇文章会把APP中登陆后,如果搞定H5登陆的问题进行阐述。

1 0