安全性

1、创建appid,appkey和appsecret

appid:应用的唯一标识
appkey:公钥=账号
appsecret:私钥=密码
1、设计一个认证系统,专用于创建第三方接入应用的账号信息,用于生成appid,appkey和appsecret,然后发appkey和appsecret给第三方接入应用,用于做认证
ps:appkey和appsecret成对出现的机制,目的在于首次验证(类似登录场景),用来申请一个token,之后请求数据请求,就直接带token请求服务端认证即可。
2、第三方接入应用自行注册,需要校验企业信息合法性(暂不考虑)

2、Token:令牌(过期失效)

1、第三方接入应用获取第一步中的appkey和appsecret
2、请求认证系统获取nonce随机数,服务端在缓存中存放下nonce
3、客户端拿到这个随机数后将其与appsecret拼接生appsecretStr,然后调用生成签名方法,传入appsecretStr,appkey,nonce,url(备注:可转大写,转小写,追加特殊字符,然后加密)进行非可逆加密(MD5/SHA1等),生成签名A。接着构造请求把签名放到请求头signature,post请求体中放入参数:appkey,nonce,timestamp,url根据request.getRequestURI()获取,调用认证接口
4、认证系统获取请求后,查询根据appkey查询缓存中的nonce,判断是否存在,不存在则提示不合法请求;判断是否相等,不等则为恶意请求。
判断timestamp的时效性,防止恶意请求:数据包中的客户端时间戳字段,然后用服务器当前时间去减客户端时间,看结果是否在一个区间内。
先根据appkey查询数据库,判断是否存在,如不存在则提示不合法用户;反之,查出appsecret,按照客户端的签名加密方式,进行加密,生成签名B,比较A和B,如果一样则生成token,失效缓存中的nonce,返回token。

3、Post请求(参数验签)

(签名校验工具)
签名生成的通用步骤如下:
第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:

  1. ◆ 参数名ASCII码从小到大排序(字典序);
  2. ◆ 如果参数的值为空不参与签名;
  3. ◆ 参数名区分大小写;
  4. ◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
  5. ◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段

第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。 注意:密钥的长度为32个字节。
参考微信支付签名工具


4、客户端IP白名单 (可选)

5、单个接口针对IP限流(令牌桶限流,漏桶限流,计数器限流)

限流是为了更好的维护系统稳定性。使用redis进行接口调用次数统计,ip+接口地址作为key,访问次数作为value,每次请求value+1,设置过期时长来限制接口的调用频率

6、记录接口请求日志

使用aop全局记录请求日志,快速定位异常请求位置,排查问题原因。
如果需要包含异步通知的接口,采用数据库方式,保存接口消费记录

  1. CREATE TABLE `t_consumer_record` (
  2. `fid` varchar(32) NOT NULL COMMENT 'id',
  3. `frecord_retry_id` bigint(20) unsigned NOT NULL COMMENT '重试记录id',
  4. `fout_trade_id` varchar(255) NOT NULL COMMENT '商户外部id',
  5. `fhandler` tinyint(3) NOT NULL DEFAULT '2' COMMENT '1 系统已处理 2 系统未处理 3 人工已处理',
  6. `fserver_url` varchar(255) NOT NULL COMMENT '服务地址',
  7. `finvoke_status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '服务调用结果 1 业务执行成功 2 通讯成功,业务执行失败 3 通讯失败,例如500 4 请求超时',
  8. `fbiz_id` varchar(64) NOT NULL COMMENT '业务id',
  9. `fid_type` tinyint(2) unsigned NOT NULL DEFAULT '0' COMMENT '业务id类型 1 订单id ',
  10. `finvoke_node` tinyint(2) unsigned NOT NULL DEFAULT '0' COMMENT '服务调用节点 1商户订单取消通知 2商户退款成功通知 3 商户审核结果通知 4商户用户支付成功通知 5物流通知 6查询订单状态 7查询售后订单 8商品属性变动通知',
  11. `frequest_param` text NOT NULL COMMENT '请求参数',
  12. `ferror_info` text COMMENT '异常详细信息',
  13. `fsimple_error` varchar(255) DEFAULT NULL COMMENT '异常简要信息',
  14. `fresponse_param` text COMMENT '响应参数',
  15. `fconsumer_type` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '消费方式 1自动调用 2 手动调用',
  16. `fcreate_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  17. `fmodify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  18. PRIMARY KEY (`fid`)
  19. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='服务接口消费记录表';

7、采用Https

1、服务端配置SSL证书
2、客户端调用https工具类忽略服务端证书校验

8、数据合法性校验

9、密码查询(加缓存,key使用客户号)

1、密码更新时,更新redis;
2、缓存查不到,查数据库,同时更新缓存;
3、密码在缓存和数据库都需要加密,返回时才解密(或者是返回客户端时,客户端自行解密)

10、接口调用失败告警

11、高可用:服务器集群部署(2-3)

客户端重试机制


幂等性

幂等性是指任意多次请求的执行结果和一次请求的执行结果所产生的影响相同。说的直白一点就是查询操作无论查询多少次都不会影响数据本身,因此查询操作本身就是幂等的。但是新增操作,每执行一次数据库就会发生变化,所以它是非幂等的。
幂等问题的解决有很多思路,这里讲一种比较严谨的。提供一个生成随机数的接口,随机数全局唯一。调用接口的时候带入随机数。
第一次调用,业务处理成功后,将随机数作为key,操作结果作为value,存入redis,同时设置过期时长。
第二次调用,查询redis,如果key存在,则证明是重复提交,直接返回错误。


数据规范

版本控制

一套成熟的API文档,一旦发布是不允许随意修改接口的。这时候如果想新增或者修改接口,就需要加入版本控制,版本号可以是整数类型,也可以是浮点数类型。一般接口地址都会带上版本号,http://ip:port/v1/list。

响应状态码规范

统一响应数据格式

为了方便给客户端响应,响应数据会包含三个属性,状态码(code)、信息描述(message)、响应数据(data)。客户端根据状态码及信息描述可快速知道接口,如果状态码返回成功,再开始处理数据。