后端Api设计的一些注意项

App所有数据都来源于服务器,App和服务器交互普遍是采用http请求接口的方式,那么在搭建和维护一个后端Api项目时候需要注意哪些问题呢?

1. 数据保护

数据保护做的好不好,有两个原则来验证:

第一,可以控制让谁来读取数据

对于任何一个Api项目其实就是只允许产品App本身访问,这就需要用密文传输请求数据,做到即使被人用抓包工具抓到请求数据也没有办法解析出参数的意义。

把安全做到更近一步,可以在加密之前引入timestamp参数,在服务端通过比较timestamp和当前时间戳可以做到对于抓取到的链接也不能重复调用。

密文的安全性取决于参数的加密方式,使用对称加密、非对称加密或者两者结合取决于团队自己的选择,当然如果接口域名已经配了证书支持https,就不用自己写代码来做这些事情了。

如果密文传输已经做到绝对安全不可破解,那就安全了吗?当然不是,你要相信安全永远是相对的。试想一下如果App源码被反编译成功,那他就可以按照代码逻辑去拼出正常的请求?另外一般业务除了有app之外也会有使用相同接口的h5版,任意一个开发者通过浏览器的调试模式很容易能分析出请求的Url以及相关参数。对于这两种情况怎么办呢?

第二,对于可以获取数据的端,也可以控制其可以获取哪些数据

既然客户端不能做到完全可靠,那就让服务端多承担一些任务,在app启动时或者用户登录时服务端先向每个客户端分发一个有固定有效期的secret,并且在服务端数据库库中存储客户端唯一标示或者uid和secret的对应关系,之后每次客户端调用都要用secret对于参数组合进行一次签名,并且确保参数包含uid,服务端接收到请求后根据uid找出对应的secret,然后按照和客户端相同的签名方式得出签名,进而和客户端传过来的签名进行比较。

因为secret是服务端分发的,即使破解了源码,再即使从本地解析出了保存的secret,那也只能获取这个secret对应的数据,再加上secret本身会有更新机制,这就使得通过破解接口来抓取大量数据变得大大的困难。

但是对于向第三方开放的api接口情况就不太一样,它不存在密文传输的问题,大体思路也是使用secret进行签名认证,只是分发secret的方式不一样,它是通过合作的方式,api提供商会给使用方分发一个key和一个secret,两者可以当成一个键值对。调用方每次调用时用secret进行参数的签名,参数包含key,服务端接收到请求,根据key参数取出secret,然后进行相同方式的签名,并且和客户端传入的签名进行对比来完成验证。

总结一下数据保护的技术点:

1.参数传输使用密文,可以使用对称加密、非对称加密、或者两者的结合,比如https请求就是属于两者结合的方式。

app端要尽量加大反编译的难度,尽量保护源码安全。

通过参数id=>secret的方式进行签名来进行用户身份认证,调用方保存自己的secret,服务端保存id和secret的对应关系,secret用于签名,后续的每次请求都要带着id参数。

在第三步的基础上,加上timestamp参数来防止连接重复调用。

2. 安全性

一些常用的安全问题都要考虑到,并且在api项目框架底层进行防范,例如xss攻击、sql注入问题、单用户或者单ip的访问频率控制来进行防cc攻击。(关于控制流量、连接并发数的访问限制和DDOS预防)

3. 旧版本兼容

互联网产品版本迭代很快,对于同一个功能新旧版本在参数或者返回值上会有差别。对于这种问题会有不同观点的解决方案,一种方案是在url中加入版本信息,比如 网页链接 , 每个版本对应一个Action,具体的业务逻辑不要写在Action层,要封装到下面的逻辑层,这样Action只需要在主业务的基础上根据需求进行扩展。另外一种观点是,这样代码重复度太高,会有大量的action文件,一个功能只提供一个唯一的url,但是要带上一个表示版本的参数,在代码框架中只有一个action,对于新旧版本的细小差别可以使用参数默认值等兼容方式进行处理,对于比较大的改动就通过逻辑判断语句进行不同处理即可。

另外比较重要的一点是,在设计之初要对业务进行足够的抽象化,让设计本身能尽量支持变化,比如以后此接口是否会增加某个分类属性,返回结果的格式是否再多包装一层就可以应对万一后面版本要增加另一块数据。

当然不可能同时维护所有旧的版本,要做到可以检测每个版本的使用情况,而且可以根据版本使客户端强制升级。对于功能的修改,同时维护旧版本是一件特别麻烦的事情,甚至有些情况不得不强制升级所有版本。

总结一下就是:

要能区分不同版本,通过url或者参数

设计时候尽量考虑之后扩展,足够抽象化

支持根据版本进行强制升级

4. 尽可能把所有业务逻辑都放到服务端

很容易理解,服务端不用发版,服务端没有处理不了的问题,很简单一个例子:用户登录功能,会有"密码错误","还没有注册"等各种异常情况的提示。那具体的文案是在客户端定义,还是从服务端直接返回给客户端比较好呢,显然是后者。因为以后提示语内容要做修改的话,放在服务端就可以很简单进行处理,放在客户端只能重新发版。

5. 返回字段可定制,可帮用户节省流量

A、B两个功能都需要获取用户信息,两者调用同一个接口,但是A只需要获取用户名和电话,就没有必要把其它信息返回给A。

6. 格式统一,易读

uri统一化,不一定说非要用restful格式,但一定要有统一的规范。

响应结果格式也统一,建议在最外层节点至少包含:code,msg,result,response_time等字段,具体的业务功能数据封装在result中。