App与后台服务器通讯安全解决方案
在开发 App 后台服务器的时候安全验证是需要考虑的问题,我们担心 API 或 AppKey 泄漏之后,第三方客户端访问我们的服务器对我们的服务或者流程造成安全危害,所以就需要保证 App 和后台应用服务器通讯过程中的安全,验证并避免发来的请求来自官方客户端还是第三方。
URL签名
因为 HTTP 协议是无状态的,在 App 客户端和服务器通信过程中不能每个请求都传输用户名和密码,所以我们一般使用token方法验证用户登录。
当服务器验证用户名和密码都正确后,生成一个随机的、唯一的、有一定时效性的 token 字符串(例如a87ff679a2f3e71d9181a67b7542122c
),在 Redis 或 Memcache 中维护一个映视表,建立 token 字符串和用户信息的对应关系表。例如,把 token 字符串 a87ff679a2f3e71d9181a67b7542122c
和用户 ID 1001
对应起来,服务器把 token 字符串返回给 App 用作身份验证的票据。
如果用户泄露了自己的url,那很大程度上 token 也被泄漏了。怎么防止 token 被泄露?使用URL签名替换token,不让 token 在网络上传输。签名的生成方式如下:
- 构造验证参数字符串 。将请求参数按照参数名字典升序排列后,把所有参数param=value用&连接起来,即类似URI中Query string的构造方式。构造好的参数字符串例如:
page=0&token=a87ff679a2f3e71d9181a67b7542122c&uid=1001
- 使用MD5方式,对上一步生成的参数字符串进行加密。
MD5("page=0&token=a87ff679a2f3e71d9181a67b7542122c&uid=1001")=ed5705d946b168f822daec0dbf934343
,其中ed5705d946b168f822daec0dbf934343
就是生成的签名 - 将上一步生成的签名sign作为请求的一个参数发送。 这一部分请求参数(需去掉token)即为
page=0&uid=1001&sign=ed5705d946b168f822daec0dbf934343
服务器接收到这个URL后,用相同的算法生成签名和sign参数对比,如果发现相等的话,就表示这个URL是有有效,那就继续执行这个API调用。
也可以将上面的算法换成下面更高级的方法:
- 构造验证参数字符串。 将请求参数按照参数名字典升序排列后,把所有参数param=value用&连接起来,即类似URI中Query string的构造方式。构造好的参数字符串例如(不包含token):
page=0&uid=1001
- 使用HMAC-SHA1方式,用token对上一步生成的参数字符串进行加密。 常见程序语言通常会内置加密函数,或通过扩展库提供支持。例如
HMAC_SHA1("page=0&uid=1001", token)
- 将上一步生成的加密串用base64编码后得到签名sign,并作为请求的一个参数发送。 假设以上参数加密后得到的sign参数为
p5ylBUpICloq82b4+Td4XzACHjU=
这一部分请求参数即为page=0&uid=1001&sign=p5ylBUpICloq82b4+Td4XzACHjU=
上面的方法还有一个问题,因为这个URL请求没有过期时间,假设别人拿到这个URL请求,就能反复调用了。
改进方法是在传递的参数中增加时间戳(timestamp)和过期时间(ttl,可选),当发现这个时间戳离现在的时间已经很久了,就判断这个URL已经失效了。
但用时间戳怎么保证App的时间和服务器的时间同步?在app每次启动时和服务器同步时间,然后在App内建一个时钟,时间戳从这个App的内部时钟获取,防止用户修改了手机的时间导致时间不一致。
于是上面(第一步和第三步中)的参数可以改为page=0&ts=1443079775&ttl=30&uid=1001
。
注意:签名失效时间参数ttl可以设置为可选参数。如果忽略这个参数,生成的签名必须默认具有有效期(例如:30分钟)。较短的有效期可以使签名更难被盗用。
服务器接收到这个URL请求,如果发现收到这个URL请求的时间已经过期,就判断出这个URL是被别人截取来反复调用了。如果时间合法,那就用算法再判断sign是否一致。
无论采用哪种安全校验方法,最终的目的在于:不论客户端是否合法,但保证业务流程合法,保证每次客户端访问的接口调用在服务端本身来说的业务层面都是合法的。
URL签名有两个缺点:
- 当用户第一次登录后token是明文返回,有被截取的风险。
- URL签名只能保护token值却没法保护其他敏感数据。例如,当用户更新自己的个人信息时,所有的信息在传输过程中应该是被加密的。
所以一般账户类接口和一些涉及到隐私的接口都是使用HTTPS协议。使用下面介绍的AES加密方法也能解决上述问题。
AES对称加密
高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法 。AES加密是一种对称加密算法,同一个密钥可以同时用作信息的加密和解密,所以使用AES加密方法需要客户端和服务器约定一个密钥,这个密钥只用一次,就是用来解密token的,以后就只是用token来做密钥。
使用AES对称加密的大致过程如下,
- 服务器中验证用户名和密码都正确后,把token字符串做AES加密和用户ID都返回给客户端,例如token字符串
a87ff679a2f3e71d9181a67b7542122c
和用户ID1001
。返回的token值(如果使用HTTP协议传输需要做Base64加密)为Base64Encode(AES("a87ff679a2f3e71d9181a67b7542122c", AES密钥))
; - APP收到token密文后通过同样的方法解决得到token明文;
- APP保存token后,以后每次机通信都通过
AES(内容, token)
传输。