标签:
想写这个系列很久了,因为之前做这个东西花费了大量的精力,有必要分享出来与大家共享。以前也写了一些关于 APP后端开发的系列文章 由于当初功力不够,很多问题描述不清楚或者解决方案过于复杂、不严谨等。
这一次查了很多资料,问了很多相关人士。准备再结合自己实际工作中的问题再次进行一些补充。就先从登陆的设计开始吧!
之前再做这一部分的时候,总想着复杂的技术,说出去多屌炸天呀。一般来说登陆的流程是:
当时对于安全性过度痴迷,确走偏了道路。首先提交的时候怕信息被人劫持,因此客户端在上传时,进行AES加密,服务端解密出结果。服务端返回的信息也会AES加密,然后客户端解密。
然后这里又带来另外一个问题:加密信息放在了客户端,那么一但客户端被反编译,hacker拿到秘钥,那么对于服务端来说加密就没有任何意义了。又为了不在客户端保存这么敏感的信息,就像秘钥由服务端下发。这样子服务端可随时对秘钥进行变更。
到这里又带来了一个新的问题,感脚一切又回到了起点:下发秘钥要走http,那么依然可能被人劫持。这时候该是加密还是怎么弄呢?如果加密,客户端又放了一个秘钥过去。那这个秘钥依然可能被人反编译。不能再从服务端获取这个秘钥吧?为了确保上个秘钥的安全,生产下一个秘钥……
当时就是陷入了这样的死循环,不可自拔。现在想想真是too young too simple!
首先在这里还是先说一下:如果你的产品刚刚起步,不要过于纠结性能、安全
先说性能:你的产品才推出的时候,冷启动的用户数一般来说不会超过1000人(这已经是很不得了的冷启动人数了)。然后你的并发也不会超过100。这种级别的访问,相信机器硬件就可以帮助你解决。如果你的条件远远超出以上规模,那么你的实力绝对足以应付即将发生的事情。
谈谈安全:安全这个事情,从一开始就要考虑,但是不能过于纠结(我之后可以讲讲我在做短信验证码这一部分的遇到的一个经历)。过早介入,会导致系统开发速度降低,过早做了一些不需要的事情(hacker来搞你也是需要成本的,在你没价值的时候,没人愿意来搞你)。所以早期应该重视开发成本,抓紧时间,早日上线。
另外,安全与性能有时候也是鱼与熊掌。
这一部分会有一些代码与图来进行说明。在安全方面逐步演进。前面说前期开发只要快就好,但是这里也要注意一个问题,就是后续升级能够弥补前面的错,要给未来升级留下余地。因为否则你的系统始终留下了一个隐患。
这是最开始的阶段,重点考虑功能实现。用户提交username + pwd 服务端验证通过后,返回一个令牌token。
这里需要注意的几个部分是要为未来的升级做好准备。我经常遇到的几个初期设计是:
- 验证通过后,把用户uid+username+salt等md5后,作为token返回到客户端。
- 对token加入时间戳,过期后客户端重新提交username + pwd验证后再发一个token到客户端
- 服务端生成一个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。
这一部分就不继续深入讨论下去了。
安全工作做得再好,如果有人能够获得大量合法用户的token,来请求你的借口,你也无法识别,因为从行为来看,这一切都是合法用户再进行。
以前为了防止别人获取到合法的信息,我才弄出了很狗血的客户端加密方法。导致后期升级的时候,诸多问题。这个东西其实很简单,使用https来进行请求(可以个人关键接口使用)
才开始做app服务端的时候,总想着token的设计。怎么才能生成一个好的token呢?现在想想真不知道当初怎么想的。
首先搞明白这个token的作用就是一个令牌,用来标记一个用户的身份。那么首先他要唯一。其次他从客户端上传后,服务端能够验证这个token是由服务端生成的。
所以token生成只要满足以上目的,你随意就好了。当然别把敏感信息暴露出去了。
常用的一种生成方式:
把上面几部分拼接起来:888:123123:abcd:1468293948
token = md5(‘888:123123:abcd:1468293948’);
对于token也有两种方法进行验证。一是:服务端生成后,将token保存起来(redis或者mysql中)。客户端穿上来之后,检查是否有该token,如果有取出对应的信息,比如uid,验证是否匹配。
另一种方法是:根据上传的uid,生成对应的token,然后进行比较token结果是否一致(要保障该算法如果给定的值一定,结果必须唯一。常用md5)。
对于个人而言更倾向于第二种方案。第一种方案效率更高(可使用redis存储这个token),但是如果redis一但雪崩,就会造成所有用户登录失效,一定时间内不可登陆。初期越简单、越可靠更好。
这一部分没有太多代码,主要是思路。还有涉及到H5的登陆问题也没有说到。下篇文章会把APP中登陆后,如果搞定H5登陆的问题进行阐述。
标签:
原文地址:http://blog.csdn.net/hel12he/article/details/51891824