在app开放接口api的设计中,避免不了的就是安全性问题,因大多数接口涉及到用户的个人信息及一些敏感的数据,所以这些接口需要进行身份的认证,那么这就需要用户提供一些信息,比如用户名密码等。
为了安全起见让用户暴露的明文密码次数越少越好,我们一般在web项目中,大多采用seesion、cookie,来保持用户的会话的有效性。
在app提供的开放接口中,后端服务器在用户登录后如何去验证和维护用户的登陆有效性呢,以下是参考项目中设计的解决方案,其原理和大多数开放接口安全验证一样,如淘宝的开放接口token验证,微信开发平台token验证。

签名设计

对于敏感的api接口,需使用https协议。
https是在http超文本传输协议加入SSL层,它在网络间通信是加密的,所以需要加密证书。
https协议需要ca证书,一般需要交费。
原理:用户登录后向服务器提供用户认证信息(如账户和密码),服务器认证完后给客户端返回一个Token令牌,用户再次获取信息时,带上此令牌,如果令牌正取,则返回数据。对于获取Token信息后,访问用户相关接口,客户端请求的url需要带上如下参数:

  1. 时间戳:timestamp
  2. 令牌:token
  3. 签名:sign

将所有用户请求的参数按照字母排序(包括timestamp,token),使用MD5加密编码(可以加点盐),全部大写,生成sign签名,这就是所说的url签名算法。登陆后每次调用用户信息时,带上sign,timestamp,token参数。
例如:原请求https://www.demo.cn/api/user/update/info.shtml?city=北京 (post和get都一样,对所有参数排序加密)
添加时间戳和token:

  1. https://www.demo.cn/api/user/update/info.shtml?city=北京&tamp=12445323134&token=wefkfjdskfjewfjkjfdfnc

url参数生成sign,最终请求如:

  1. https://www.demo.cn/api/user/update/info.shtml?city=北京&tamp=12445323134&token=wefkfjdskfjewfjkjfdfnc&sign=FDK2434JKJFD334FDF

其最终的原理是减小明文的暴露次数;保证数据安全的访问。
具体实现如下:
1、客户端向服务器端发送用户认证信息(用户名和密码),服务器端收到请求后,验证用户信息是否正确。
正确:则返回一个唯一不重复的字符串(一般为Uid),然后在Redis(任意缓存服务器)中维护Token—-Uid的用户信息关系,以便其他api对token的校验。
错误:则返回错误码。
2、服务器设计一个url请求拦截规则
(1)判断是否包含timestamp、token、sign参数,如果不包含,返回错误码。
(2)判断服务器接到请求的时间和参数中的时间戳是否相差很长一段时间(时间自定义如半个小时),如果超过则说明该 url 已经过期(如果 url 被盗,他改变了时间戳,但是会导致sign签名不相等,这种情况需使用自己的加密方法)。
(3)判断token是否有效,根据请求过来的token,查询redis缓存中的uid,如果获取不到这说明该token已过期。
(4)根据用户请求的url参数,服务器端按照同样的规则生成sign签名,对比签名看是否相等,相等则放行。(自然 url 签名也无法100%保证其安全,也可以通过公钥AES对数据和url加密,但这样如果无法确保公钥丢失,所以签名只是很大程度上保证安全)。
(5)此 url 拦截只需对获取身份认证的 url 放行(如登陆url),剩余所有的url都需拦截。
3、Token和Uid关系维护
对于用户登录我们需要创建token–uid的关系,用户退出时需要需删除token–uid的关系。

案例说明

某财富会APP代码及说明:

  1. // 验证必需参数
  2. $validator = Validator::make($request->all(), [
  3. 'device_id' => 'required',
  4. 'device_type' => 'required|in:ios,android',
  5. 'timestamp' => 'required',
  6. 'sign' => 'required'
  7. ]);
  8. if($validator->fails()) return $this->failed('参数错误,请求失败', 402);
  9. // 处理参数
  10. // 删除laravel中自带参数
  11. // 签名赋值变量$sign,删除参数中的签名sign
  12. $params = $request->all();
  13. $sign = $params['sign'];
  14. unset($params['sign']);
  15. unset($params['actionUrl']);
  16. unset($params['csrf-token']);
  17. // 处理url格式
  18. // 样式:device_id=866351034455402&device_type=android&app_version=1.00111&api_token=21c6da6881a940d18bb46b0a6df3b6ad&member_id=1068&production=Xiaomi&system_version=7.0&channel=chat&device_model=MI 5&device_token=AgXMPCctGoy3VXSeK3Oe_HIvy7uGEhQkI5_j8aEYiGH1&mobile=&referer_mobile=&code=&content=&page=1&city_id=1¬ice_type=normal&order_id=1&share_model=activity&id=1&activity_id=1&asset_id=1&product_id=24×tamp=1531289355&sign=sign&ZGIzN^WMzZG!VkY@2FkMDl@mNWIz!ZDR#lYTczOGFjZDQ5M2Q^
  19. $str = '';
  20. foreach ($params as $key => $value) {
  21. $str .= sprintf('%s=%s&', $key, $value);
  22. }
  23. // 加密信息
  24. $info = System::$version;
  25. if(isset($info[$request->device_type][$request->app_version])){
  26. $security = $info[$request->device_type][$request->app_version];
  27. } else {
  28. $security = current($info[$request->device_type]);
  29. }
  30. $str .= $security['security'];
  31. if($sign !== md5($str)) return $this->failed('校验错误', 402);

sign签名生成说明:所有参数按提交顺序拼接成字符串,如:

  1. a=param&b=param&c=param×tamp=1531289355&ZGIzN^WMzZG!VkY@2FkMDl@mNWIz!ZDR#lYTczOGFjZDQ5M2Q^

其中,ZGIzN^WMzZG!VkY@2FkMDl@mNWIz!ZDR#lYTczOGFjZDQ5M2Q^为后台提供加密盐,每个版本对应不同的加密盐,拼接完成后,进行MD5编码加密,生成sign签名。