API 接口设计最佳实践

发布于: 2 小时前
API接口设计最佳实践

前言

最近团队内部在做故障复盘的时候发现有很多故障都是因为接口设计不当导致的,这里我就整理归纳一下在接口设计层面需要注意的地方。


API接口设计

Token设计

Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。

Token的值一般用UUID(算法比较著名的有雪花算法),当服务端接收到客户端请求后会生成Token(一串字符,如etye0fgkgk4ca2ttdsl0ae9a5dd77471fgf),然后将Token作为key将一些和Token关联的信息作为value保存到如Redis缓存数据库中,同步把该Token返回给客户端;后续该客户端的请求都需要带上这个Token,服务器收到请求后就会去缓存服务器中匹配这个Token是否存在,存在则调用接口,不存在返回接口错误。

Token种类

API Token(接口令牌): 一般用于访问不需要用户登录的接口,如登录、注册、一些基本数据的获取(如信用卡官网的如信用卡费率相关信息)等。获取接口令牌需要拿appId、timestamp和sign来换;其中该sign值一般是把timestamp、key和对应的参数先进行字母排序再进行MD5加密(有时候会加盐),即sign=MD5(排序(timestamp+key+参数));

  • 假设API的请求参数为channel:T,discount:90%,quantities:10,根据参数名称的ASCII码表的顺序排序即为:channel:T,discount:90%,quantities:10。

  • 接着把排序后的参数名和参数值拼装在一起为:channelTdiscount90%quantities10。

  • 把拼装好的字符串采用utf-8编码,使用签名算法对编码后的字节流进行摘要,即为sign=md5(channelTdiscount90%quantities10);

  • 最后,Token=hex(appid+sign+timestamp+salt),即可获得十六进制的一串字符,如“68656C6C6F776F726C64”。

 

USER Token(用户令牌): 用于访问需要用户登录之后的接口,如:获取我的基本信息、保存、修改、删除等操作。获取用户令牌需要拿用户名和密码来换。

API接口设计原则

1、明确协议规范

在设计初期需要明确双方的通讯协议是TCP、HTTP、RPC,一般针对比较敏感的交易或者行业(如金融业),建议使用HTTPS协议以确保数据交互的安全。

2、统一接口路径规范

建议采用Restful的风格,一般采用这样的格式:控制器名/方法名。具体请参考以下例子:

POST /recommend/cardlist

3、统一接口版本管理

APP后台逻辑总是处于变化当中,但是APP端(如安卓和ios)因为涉及到应用市场的审核问题,还有这些2C端的APP应用存在版本碎片化的问题,因此后台暴露的接口需要在一段时间内支持不同版本的接口,一般方法是通过Nginx通过配置过滤根据接口的不同版本进行路由分发。

一般来说,接口的版本管理一般有以下两种方法:

  1. 在URL中加入version信息,如下述;

  2. 在HTTP header加入version信息,这样就等于只有一个接口,但是具体的不同版本的业务逻辑由后台区分处理。

POST v1/recommend/cardlist

Nginx的路由分发:

server {

listen 80;

server_name vip.com;

location /v1/ {

proxy_pass http://129.0.0.1:8001/;

proxy_redirect http://129.0.0.1:8001/ /v1/;

proxysetheader Host $host;

}

location /v2/ {

proxy_pass http://129.0.0.1:8002/;

proxy_redirect http://129.0.0.1:8002/ /v2/;

proxysetheader Host $host;

}

}

server {

listen 8001;

allow 129.0.0.1;

deney all;

server_name vip.com;

root vip.com/v1/;

}

server {

listen 8002;

allow 129.0.0.1;

deney all;

server_name vip.com;

root vip.com/v2/;

}

4、为你的接口设定调用门槛

为调用你的系统分配一个ID和key,针对每个请求对ID和key进行校验,避免在企业内网中的其他系统只要知道接口被可以随意调用。

5、接口返回规范

返回数据尽量统一规范,务必包括:返回码、返回信息、数据。

{

"code" : 0,

"content" : "string", <- 这里为JSON

"message" : "string"

}

6、接口安全规范

当我们开发的接口需要暴露到公网,这样的风险跟我们在企业内网暴露给其他系统调用的风险是不可同日而语的。其中有很多风险需要我们一一解决。以下仅提供能想到的:

6.1.数据如何防止被看到?

目前业界老生常谈就是对称加密和非对称加密。

对称加密:对称密钥在加密和解密的过程中使用的密钥是相同的,常见的对称加密算法有DES,AES;优点是计算速度快,缺点是在数据传送前,发送方和接收方必须商定好秘钥,然后使双方都能保存好秘钥,如果一方的秘钥被泄露,那么加密信息也就不安全了;

非对称加密:服务端会生成一对密钥,私钥存放在服务器端,公钥可以发布给任何人使用;优点就是比起对称加密更加安全,但是加解密的速度比对称加密慢太多了;广泛使用的是RSA算法;

目前主流的做法是在传输层使用https协议,http和tcp之间添加一层加密层(SSL层),这一层负责数据的加密和解密。https协议则是巧妙的利用上述两种对称加密方法;浅显一点说就是客户端和服务端建立三次握手连接过程中通过交换双方非对称公钥,接着使用对方非对称公钥加密双方约定好的对称密钥,这样就只有双方有这个对称密钥(这样的非对称加密可以保证很安全的把对称密钥给到对方)。后续双方的报文沟通就可以使用该对称密钥进行加解密(这样的对称加密可以保证请求报文可以快速被解密处理,并在处理后被快速加密响应回去)。

6.2.数据如何防止给篡改?

这个时候我们需要对数据进行加签,数据签名平时用得比较多的是MD5,即将需要提交的数据通过某种方式组合和一个字符串,然后通过MD5生成一段加密字符串,这段加密字符串就是数据包的签名。具体请看以下的图。

6.3.时间戳机制

如果加密数据被抓包后被用于重放攻击,我们怎么办?这个时候我们可以把解密后的URL参数中的时间戳与系统时间进行比较,如果时间差超过一定间距(如5分钟)即认为该报文被劫持并返回错误。但是,务必保证该时间戳的超时时间一定要跟sign保存的有效时间一致。

客户端在第一次访问服务端时,服务端将sign缓存到Redis中并把有效时间设定为跟时间戳的超时时间一致;如果有人使用同一个URL再次访问,如果发现缓存服务器中已经存在了本次的sign,则拒绝服务;如果在Redis中的sign失效的情况下,有人使用同一个URL再次访问,则会被时间戳超时机制拦截。这样的话,就可以避免URL被别人截获后的重放攻击。

整个流程如下:

1、客户端通过用户名密码登录服务器并获取Token

2、客户端生成时间戳timestamp,并将timestamp作为其中一个参数

3、客户端将所有的参数,包括Token和timestamp按照自己的算法进行排序加密得到签名sign

4、将token、timestamp和sign作为请求时必须携带的参数加在每个请求的URL后边(http://url/request?token=123&timestamp=123&sign=123123123)

5、服务端写一个过滤器对token、timestamp和sign进行验证,只有在token有效、timestamp未超时、缓存服务器中不存在sign三种情况同时满足,本次请求才有效。

6.4.随机数机制

另外,一般会在URL参数上加上随机数(即所谓的加盐)并与6.3的时间戳机制组合使用以便提升防重复提交攻击。

6.5.黑名单机制

针对同一个IP在短时间内频繁请求的,可以通过Nginx进行过滤,同步可以在Nginx部署动态黑名单(即IP实时更新到黑名单库),这样可以防控少量的DDOS。但受限于判断黑名单需要考虑多维度的信息,一般我们的Nginx尽量只做同一IP校验,更多维度的黑名单校验可以通过厂商去解决。

6.6.数据合法性校验

这里的数据合法性校验主要指的是数据格式校验和业务规则校验。

  • 数据格式校验:日期格式校验、长度校验、非空校验等;

  • 业务规则校验:如库存校验、身份证合法性校验等。

7、幂等性

定义:在计算机中,表示对同一个过程应用相同的参数多次和应用一次产生的效果是一样,这样的过程即被称为满足幂等性。

具体的解决方案有token机制、分布式锁、状态机等方案;这里引用一下之前看到的一篇文章,写得比较详细: https://blog.csdn.net/u011635492/article/details/81058153

8、接口设计的一些最佳实践

  • 即使返回的JSON中某字段没有值(即空值),也一定要返回该字段。同时前端也需做好这类情况的容错处理;

  • 针对单页面的多接口请求,为避免扩大攻击面,建议把多接口逻辑整合到一个接口,一个页面直接调用该接口,以避免绕过部分接口进行攻击;

  • 接口最好支持分页;分页一般有电梯式分页(即一开始算好总页数,优劣也一目了然)和游标式分页(即每次查询会拿上一页的最大的那个ID即cursor进行查询,这种方式更适合类似以时间为排序条件的互联网单页应用);

  • 针对你的接口提前做好限流;一般常用的限流有计数器、令牌桶、漏桶这三种。具体请参考接口中的几种限流实现

API接口管理

一家公司的每个系统都会有各种各样的接口,但是大部分公司,特别是传统行业的公司的所谓接口文档更多是当每个系传统的word文本格式,这种传统的格式有着人尽皆知的痛点:

  1. 维护不及时;

  2. 与代码不同步;

  3. 归档后“便束之高阁”;

  4. 接口文档跟代码没有互动;

  5. 文本检索无法建立全局搜索,需要额外借助工具。

为了解决上述的问题,需要建立一套行之有效的接口管理体系,该体系的目标是:

  1. 能够进行接口文档管理,作为后续的接口治理的其中一部分;

  2. 能作为接口测试的平台,这样能保证接口跟代码是同步的;

  3. 支持文本检索。

业界有很多不同的API接口管理平台。如去哪儿网的YAPI平台、阿里某团队开发的RAP平台、Swagger、easyAPI。目前个人在试用YAPI平台,后面补充具体的教程与使用体验吧。

发布于: 2 小时前 阅读数: 54
用户头像

Man

关注

尘世间一名迷途小码农 2020.06.24 加入

1、热衷于用技术思维去解决问题,厌恶低效,热衷自动化和智能化,释放人的创造性。 2、CSDN博客:https://blog.csdn.net/justyman

评论

发布
暂无评论
API接口设计最佳实践