API接口规范设计流程

177次阅读
没有评论

在实际工作中,我们需要经常跟第三方平台打交道,可能会对接第三方平台 API 接口,或者提供 API 接口给第三方平台调用。

那么问题来了,如果设计一个优雅的 API 接口,能够满足:安全性、可重复调用、稳定性、好定位问题等多方面需求?

今天跟大家一起聊聊设计 API 接口时,需要注意的一些地方,希望对你会有所帮助。

1. 签名

为了防止 API 接口中的数据被篡改,很多时候我们需要对 API 接口做签名。

接口请求方将请求参数 + 时间戳 + 密钥拼接成一个字符串,然后通过 md5 等 hash 算法,生成一个前面 sign。

然后在请求参数或者请求头中,增加 sign 参数,传递给 API 接口。

API 接口的网关服务,获取到该 sign 值,然后用相同的请求参数 + 时间戳 + 密钥拼接成一个字符串,用相同的 m5 算法生成另外一个 sign,对比两个 sign 值是否相等。

如果两个 sign 相等,则认为是有效请求,API 接口的网关服务会将给请求转发给相应的业务系统。

如果两个 sign 不相等,则 API 接口的网关服务会直接返回签名错误。

问题来了:签名中为什么要加时间戳?

答:为了安全性考虑,防止同一次请求被反复利用,增加了密钥没破解的可能性,我们必须要对每次请求都设置一个合理的过期时间,比如:15 分钟。

这样一次请求,在 15 分钟之内是有效的,超过 15 分钟,API 接口的网关服务会返回超过有效期的异常提示。

目前生成签名中的密钥有两种形式:

一种是双方约定一个固定值 privateKey。

另一种是 API 接口提供方给出 AK/SK 两个值,双方约定用 SK 作为签名中的密钥。AK 接口调用方作为 header 中的 accessKey 传递给 API 接口提供方,这样 API 接口提供方可以根据 AK 获取到 SK,而生成新的 sgin。

2. 加密

有些时候,我们的 API 接口直接传递的非常重要的数据,比如:用户的银行卡号、转账金额、用户身份证等,如果将这些参数,直接明文,暴露到公网上是非常危险的事情。
由此,我们需要对数据进行加密。

目前使用比较多的是用 BASE64 加解密。

我们可以将所有的数据,安装一定的规律拼接成一个大的字符串,然后在加一个密钥,拼接到一起。

然后使用 JDK1.8 之后的 Base64 工具类处理,效果如下:

【加密前的数据】www.baidu.com

【加密后的数据】d3d3LmJhaWR1LmNvbQ==

为了安全性,使用 Base64 可以加密多次。

API 接口的调用方在传递参数时,body 中只有一个参数 data,它就是 base64 之后的加密数据。

API 接口的网关服务,在接收到 data 数据后,根据双方事先预定的密钥、加密算法、加密次数等,进行解密,并且反序列化出参数数据。

3.ip 白名单

为了进一步加强 API 接口的安全性,防止接口的签名或者加密被破解了,攻击者可以在自己的服务器上请求该接口。

需求限制请求 ip,增加 ip 白名单。

只有在白名单中的 ip 地址,才能成功请求 API 接口,否则直接返回无访问权限。

ip 白名单也可以加在 API 网关服务上。

但也要防止公司的内部应用服务器被攻破,这种情况也可以从内部服务器上发起 API 接口的请求。

这时候就需要增加 web 防火墙了,比如:ModSecurity 等。

4. 限流

如果你的 API 接口被第三方平台调用了,这就意味着着,调用频率是没法控制的。

第三方平台调用你的 API 接口时,如果并发量一下子太高,可能会导致你的 API 服务不可用,接口直接挂掉。

由此,必须要对 API 接口做限流。

限流方法有三种:

对请求 ip 做限流:比如同一个 ip,在一分钟内,对 API 接口总的请求次数,不能超过 10000 次。

对请求接口做限流:比如同一个 ip,在一分钟内,对指定的 API 接口,请求次数不能超过 2000 次。

对请求用户做限流:比如同一个 AK/SK 用户,在一分钟内,对 API 接口总的请求次数,不能超过 10000 次。

我们在实际工作中,可以通过 nginx,redis 或者 gateway 实现限流的功能。

5. 参数校验

我们需要对 API 接口做参数校验,比如:校验必填字段是否为空,校验字段类型,校验字段长度,校验枚举值等等。

这样做可以拦截一些无效的请求。

比如在新增数据时,字段长度超过了数据字段的最大长度,数据库会直接报错。

但这种异常的请求,我们完全可以在 API 接口的前期进行识别,没有必要走到数据库保存数据那一步,浪费系统资源。

有些金额字段,本来是正数,但如果用户传入了负数,万一接口没做校验,可能会导致一些没必要的损失。

还有些状态字段,如果不做校验,用户如果传入了系统中不存在的枚举值,就会导致保存的数据异常。

由此可见,做参数校验是非常有必要的。

在 Java 中校验数据使用最多的是 hiberate 的 Validator 框架,它里面包含了 @Null、@NotEmpty、@Size、@Max、@Min 等注解。

用它们校验数据非常方便。

当然有些日期字段和枚举字段,可能需要通过自定义注解的方式实现参数校验。

6. 统一返回值

我之前调用过别人的 API 接口,正常返回数据是一种 json 格式,比如:

{"code":0, "message":null, "data":[{"id":123,"name":"abc"}] }

签名错误返回的 json 格式:

{"code":1001, "message":"签名错误", "data":null}

没有数据权限返回的 json 格式:

{"rt":10, "errorMgt":"没有权限", "result":null}

这种是比较坑的做法,返回值中有多种不同格式的返回数据,这样会导致对接方很难理解。

出现这种情况,可能是 API 网关定义了一直返回值结构,业务系统定义了另外一种返回值结构。如果是网关异常,则返回网关定义的返回值结构,如果是业务系统异常,则返回业务系统的返回值结构。

但这样会导致 API 接口出现不同的异常时,返回不同的返回值结构,非常不利于接口的维护。

其实这个问题我们可以在设计 API 网关时解决。

业务系统在出现异常时,抛出业务异常的 RuntimeException,其中有个 message 字段定义异常信息。

所有的 API 接口都必须经过 API 网关,API 网关捕获该业务异常,然后转换成统一的异常结构返回,这样能统一返回值结构。

7. 统一封装异常

我们的 API 接口需要对异常进行统一处理。

不知道你有没有遇到过这种场景:有时候在 API 接口中,需要访问数据库,但表不存在,或者 sql 语句异常,就会直接把 sql 信息在 API 接口中直接返回。

返回值中包含了异常堆栈信息、数据库信息、错误代码和行数等信息。

如果直接把这些内容暴露给第三方平台,是很危险的事情。

有些不法分子,利用接口返回值中的这些信息,有可能会进行 sql 注入或者直接脱库,而对我们系统造成一定的损失。

因此非常有必要对 API 接口中的异常做统一处理,把异常转换成这样:

{"code":500, "message":"服务器内部错误", "data":null}

返回码 code 是 500,返回信息 message 是服务器内部异常。

这样第三方平台就知道是 API 接口出现了内部问题,但不知道具体原因,他们可以找我们排查问题。

我们可以在内部的日志文件中,把堆栈信息、数据库信息、错误代码行数等信息,打印出来。

我们可以在 gateway 中对异常进行拦截,做统一封装,然后给第三方平台的是处理后没有敏感信息的错误信息。

8. 请求日志

在第三方平台请求你的 API 接口时,接口的请求日志非常重要,通过它可以快速的分析和定位问题。

我们需要把 API 接口的请求 url、请求参数、请求头、请求方式、响应数据和响应时间等,记录到日志文件中。

最好有 traceId,可以通过它串联整个请求的日志,过滤多余的日志。

当然有些时候,请求日志不光是你们公司开发人员需要查看,第三方平台的用户也需要能查看接口的请求日志。

这时就需要把日志落地到数据库,比如:mongodb 或者 elastic search,然后做一个 UI 页面,给第三方平台的用户开通查看权限。这样他们就能在外网查看请求日志了,他们自己也能定位一部分问题。

9. 幂等设计

第三方平台极有可能在极短的时间内,请求我们接口多次,比如:在 1 秒内请求两次。有可能是他们业务系统有 bug,或者在做接口调用失败重试,因此我们的 API 接口需要做幂等设计。

也就是说要支持在极短的时间内,第三方平台用相同的参数请求 API 接口多次,第一次请求数据库会新增数据,但第二次请求以后就不会新增数据,但也会返回成功。

这样做的目的是不会产生错误数据。

我们在日常工作中,可以通过在数据库中增加唯一索引,或者在 redis 保存 requestId 和请求参来保证接口幂等性。

对接口幂等性感兴趣的小伙伴,可以看看我的另一篇文章《高并发下如何保证接口的幂等性?》,里面有非常详细的介绍。

10. 限制记录条数

对于对我提供的批量接口,一定要限制请求的记录条数。

如果请求的数据太多,很容易造成 API 接口超时等问题,让 API 接口变得不稳定。

通常情况下,建议一次请求中的参数,最多支持传入 500 条记录。

如果用户传入多余 500 条记录,则接口直接给出提示。

建议这个参数做成可配置的,并且要事先跟第三方平台协商好,避免上线后产生不必要的问题。

11. 压测

上线前我们务必要对 API 接口做一下压力测试,知道各个接口的 qps 情况。

以便于我们能够更好的预估,需要部署多少服务器节点,对于 API 接口的稳定性至关重要。

之前虽说对 API 接口做了限流,但是实际上 API 接口是否能够达到限制的阀值,这是一个问号,如果不做压力测试,是有很大风险的。

比如:你 API 接口限流 1 秒只允许 50 次请求,但实际 API 接口只能处理 30 次请求,这样你的 API 接口也会处理不过来。

我们在工作中可以用 jmeter 或者 apache benc 对 API 接口做压力测试。

12. 异步处理

一般的 API 接口的逻辑都是同步处理的,请求完之后立刻返回结果。

但有时候,我们的 API 接口里面的业务逻辑非常复杂,特别是有些批量接口,如果同步处理业务,耗时会非常长。

这种情况下,为了提升 API 接口的性能,我们可以改成异步处理。

在 API 接口中可以发送一条 mq 消息,然后直接返回成功。之后,有个专门的 mq 消费者去异步消费该消息,做业务逻辑处理。

直接异步处理的接口,第三方平台有两种方式获取到。

第一种方式是:我们回调第三方平台的接口,告知他们 API 接口的处理结果,很多支付接口就是这么玩的。

第二种方式是:第三方平台通过轮询调用我们另外一个查询状态的 API 接口,每隔一段时间查询一次状态,传入的参数是之前的那个 API 接口中的 id 集合。

13. 数据脱敏

有时候第三方平台调用我们 API 接口时,获取的数据中有一部分是敏感数据,比如:用户手机号、银行卡号等等。

这样信息如果通过 API 接口直接保留到外网,是非常不安全的,很容易造成用户隐私数据泄露的问题。

这就需要对部分数据做数据脱敏了。

我们可以在返回的数据中,部分内容用星号代替。

已用户手机号为例:182****887。

这样即使数据被泄露了,也只泄露了一部分,不法分子拿到这份数据也没啥用。

14. 完整的接口文档

说实话,一份完整的 API 接口文档,在双方做接口对接时,可以减少很多沟通成本,让对方少走很多弯路。

接口文档中需要包含如下信息:

  • 接口地址
  • 请求方式,比如:post 或 get
  • 请求参数和字段介绍
  • 返回值和字段介绍
  • 返回码和错误信息
  • 加密或签名示例
  • 完整的请求 demo
  • 额外的说明,比如:开通 ip 白名单。

接口文档中最好能够统一接口和字段名称的命名风格,比如都用驼峰标识命名。

统一字段的类型和长度,比如:id 字段用 Long 类型,长度规定 20。status 字段用 int 类型,长度固定 2 等。

统一时间格式字段,比如:time 用 String 类型,格式为:yyyy-MM-dd HH:mm:ss。
接口文档中写明 AK/SK 和域名,找某某单独提供等。

作者: 苏三说技术

正文完
有偿技术支持加微信
post-qrcode
 
评论(没有评论)
验证码