如何设计一个良好的API接口
提供给第三方的业务接口应该如何设计呢?需要从哪些方面考虑?以及如何实现这些方面?
1、标准化
RESTful
2、安全性
1)请求token(防止接口被第三方调用)
token作为调用系统的凭证。token可以设置一次有效(安全性最高,完全防止接口被第三方调用),不过推荐设置时效性,减少获取获取token接口的请求频率。 token建议放在请求头上,这样可以跟业务参数完全区分开。
获取token一般会涉及的几个参数:appId、appKey、timestamp、nonce(requestId)、sign
appId和appKey通过开发平台申请和下发,appId即应用Id,是请求方的全局唯一的标识,一个appId对应一个客户,appKey需要高度保密,用于接口加密时拼装appKey作为加密串,不作为网络传输。
timestamp是时间戳,目的是为了减轻DOS攻击。防止请求被拦截后一直尝试请求接口。服务器设置时间戳阀值(5s),如果请求时间戳和服务器时间超过阀值,则响应失败。
nonce(requestId)是随机值,目的是为了增加sign的多变性,也可以保护接口的幂等性,相邻的两次请求nonce不允许重复,如果重复则认为是重复提交,响应失败。(将每次请求的nonce参数存储到缓存中,每次请求去缓存中查询是否存在,如果存在则认为是非法请求)
sign是参数签名,将appId,nonce,timestamp,还有其余非空请求参数,按照字母升序排列,然后使用URL键值对的格式(key1=value1&key2=value2)拼接起来,最后再拼接上appKey,组成待签名串;然后进行md5加密生成签名(或其他不可逆加密方式(如RSA2),有的情况要求先对待签名串进行UrlEncode,然后再加密生成签名)
请求携带appId和sign,只有拥有合法的身份appId和正确的签名sign才放行。
2)客户端IP白名单(防止接口被第三方调用)
为了提高应用访问的安全性。开发者可以通过IP白名单功能设置能够合法访问服务端API的IP列表(获取API访问凭证相关接口除外),不在IP白名单列表中的来源IP的请求会被拒绝。
输入的IP地址应为IPv4格式,不能为IP通配符,必须为公网服务器IP
3)加密签名(加密防止隐私数据泄露,签名防止数据被篡改)
对敏感入参进行AES加密,防止数据泄漏
对所有请求参数拼装后进行RSA签名,防止参数遭篡改。请求进来之后先验证签名,签名不匹配直接返回失败
(对于客户端与服务端的交互,AES密钥应使用动态密钥,每次由客户端生成AES密钥,并使用公钥进行加密传给服务端(RSA私钥保存在服务端))
4)限流
限流是为了更好的维护系统稳定性。防止接口遭恶意大量频繁调用(盗刷)。使用Redis进行接口调用次数统计,ip+接口地址作为key,访问次数作为value,每次请求value+1,设置过期时长来限制接口的调用频率。
可使用阿里的Sentinel限流工具:https://github.com/alibaba/Sentinel
3、幂等性
幂等性是指任意多次请求的执行结果和一次请求的执行结果所产生的影响相同。
如查询删除是幂等的,新增修改是非幂等的。
解决方案:
服务方提供一个生成全局唯一随机数token的接口,客户端在调用业务接口前先向服务方发送请求获取token,在客户端获取token的时候,将token存入redis中。
① 服务方提供获取token的接口,token可以是uuid
② 客户端在调用业务接口前先调用接口获取token
③ 服务方在客户端获取token时,生成token,并将token 作为 key,客户端用户信息作为value,保存在redis中,然后返回token
④ 客户端请求业务接口时,将获取的token放在header(最好放在header中)或者作为请求参数请求接口
⑤ 服务端收到请求后,从headers中拿到token,然后根据token到redis中查找该key是否存在
⑥ 如果存在,业务处理成功后,从redis中删除此token。如果不存在,说明重复调用,返回请勿重复操作即可
注意:从redis中查询token和删除token,要保证原子性,应使用redis分布式锁
https://www.cnblogs.com/yangyongjie/p/14569539.html
4、规范标准
1)加密、签名方式规范
敏感字段采用AES加密,模式为CBC,具体算法为 AES/CBC/PKCS5Padding。
签名的作用:对数据进行签名后,可以保证数据完整性,机密性和发送方角色的不可抵赖性,可以有效防止请求信息信息被篡改
签名方式采用RSA2(签名算法为SHA256WithRSA,要求RSA密钥的长度至少为2048位),步骤如下:
1)筛选并排序
获取所有请求参数,不包括字节类型参数(如文件,字节流),剔除sign参数和值为空的参数,并且参数名和参数值前后不要带有空格,并按照参数名的第一个字符的键值ASCII码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值 ASCII 码递增排序,以此类推;
2)拼接
将排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用 & 字符连接起来,此时生成的字符串为待签名字符串
3)签名
最后对待签名串签名,并进行BASE64编码。然后将生成的签名赋值给sign参数,拼接到请求参数中
2)请求方式为POST
除了sign参数外,其他参数全部以JSON格式放在body中。
请求方需要对sign进行UrlEncode,且Content-Type:application/json; charset=utf-8
3)接口返回规范
返回类型为JSON格式,包含状态码code、描述msg、响应数据data,返回码和描述提前定义好。必要的话,对响应的参数进行签名赋值给sign参数一并返回。
4)统一参数校验和验签,打印请求日志
使用AOP全局记录请求日志(请求接口URL,请求参数),响应日志。设置请求线程号,用于快速定位异常请求位置,排查问题原因
使用另一个AOP统一进行参数非空校验、签名校验、token校验,不通过直接返回失败,不进入业务代码。
5、接口设计示例
如何调用API
-
-
- access_token
- ip白名单
- 签名机制
-
$ curl -X POST 'https://api_host/api_path?sign=xxx'
-H 'Authorization:<这里替换为对应的access token>'
-H 'content-type:application/json; charset=utf-8'
-d '{
"field": "value"
}'
{ "code": 0, "msg": "success", "data": { // 响应的具体数据内容 } }
5、返回码说明
返回码:code
|
返回描:msg
|
说明
|
0000
|
success
|
成功
|
9991
|
invalid token
|
查看token是否填写正确,是否过期
|
9992
|
verify sign fail
|
验签失败,检查签名是否按照约定
|
9993
|
no sign
|
没有对参数进行签名
|
9994
|
method not allowed
|
HTTP 方法不支持,检查是否是POST 请求
|
9995
|
empty param
|
参数为空
|
9996
|
invalid param
|
请求参数有误,请检查参数的正确性
|
9997
|
deal fail
|
请求处理失败,请检查参数并重试
|
9998
|
internal error
|
内部错误,请稍后重试,仍然出现请联系小米开发
|
9999
|
service error
|
服务异常,检查请求参数是否正确,确认无误联系小米开发
|
获取访问凭证接口
①、概述:要访问API,需要先获取相应的访问凭证(access_token),作为API调用时的鉴权,在调用内容推送API时,被调用方可据此识别调用方的身份
②、说明:access_token具有一定的时效性,默认最长有效期为3600秒,在需要的情况下,合作方需要调用此接口来获取一个新的access_token
③、请求URL/Method
HTTP URL | http://api_host/auth/v1/get_access_token?nonce=xxx&partnerId=xxx×tamp=xxx?sign=xxx |
HTTP Method | GET |
④:请求参数
字段名
|
字段类型
|
最大长度限制
|
是否必填
|
说明
|
partnerId
|
String
|
32
|
是
|
分配的合作方唯一的标识符
|
timestamp
|
Long
|
-
|
是
|
当前请求时间戳,单位秒
|
nonce
|
String
|
32
|
是
|
随机值,相邻两次请求的nonce不允许重复,如果重复认为重复提交,响应失败
|
sign
|
String
|
256
|
是
|
签名,签名字段按照ASCII码递增排序,组合成“参数=参数值”的格式,并把这些参数用&字符拼接起来,最后再拼接上 partnerKey=xxx,组成签名串,进行MD5加密生成sign。
如:MD5(nonce=xxx&partnerId=xxx×tamp=xxx&partnerKey=xxx)
|
⑤:响应参数
字段名
|
类型
|
最大长度
|
是否必填
|
说明
|
公共响应参数
|
||||
code
|
String
|
-
|
是
|
返回码
|
msg
|
String
|
-
|
是
|
返回码描述
|
以下为data元素内容
|
||||
access_token
|
String
|
40
|
是
|
访问令牌,通过该令牌调用需要授权类接口
|
expires_in
|
String
|
16
|
是
|
该令牌的有效时间,单位是秒
|
示例:
{ "code": 0, "msg": "success", "data": { "access_token":"c83a2e753d41494fbde7b4aa31f4fa66", "expires_in":"7200" } }
补充,微信小程序 access_token获取接口:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html
详情可参考微信公众平台文档 《获取access_token》
接口:getAccessToken
请求方式:GET(HTTPS)
接口调用请求说明
https请求方式: GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
请求参数:
属性 | 类型 | 必填 | 说明 |
---|---|---|---|
grant_type | string | 是 | 填写 client_credential |
appid | string | 是 | 小程序唯一凭证,即 AppID,可在「微信公众平台 - 设置 - 开发设置」页中获得。(需要已经成为开发者,且帐号没有异常状态) |
secret | string | 是 | 小程序唯一凭证密钥,即 AppSecret,获取方式同 appid |
响应参数:
属性 | 类型 | 说明 |
---|---|---|
access_token | string | 获取到的凭证 |
expires_in | number | 凭证有效时间,单位:秒。目前是7200秒之内的值。 |
access_token的说明:
①、access_token 的存储至少要保留 512 个字符空间
②、access_token 的有效期目前为 2 个小时,需定时刷新,重复获取将导致上次获取的 access_token 失效
③、建议开发者使用中控服务器统一获取和刷新 access_token,其他业务逻辑服务器所使用的 access_token 均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突,导致 access_token 覆盖而影响业务
④、access_token 的有效期通过返回的 expires_in 来传达,目前是7200秒之内的值,中控服务器需要根据这个有效时间提前去刷新。在刷新过程中,中控服务器可对外继续输出的老 access_token,此时公众平台后台会保证在5分钟内,新老 access_token 都可用,这保证了第三方业务的平滑过渡;
⑤、access_token 的有效时间可能会在未来有调整,所以中控服务器不仅需要内部定时主动刷新,还需要提供被动刷新 access_token 的接口,这样便于业务服务器在 API 调用获知 access_token 已超时的情况下,可以触发 access_token 的刷新流程
END.