在日常的开发中,会接触到各种接口,有前后端数据传输接口,第三方平台接口。一个平台的前后端数据传输接口一般都会在内网环境下对接,而且也会使用安全框架,所以安全性可以得到较好的保障。但第三方平台接口是外部接口,要对接第三方平台API接口,或者提供API接口给第三方平台调用,这时我们就需要考虑多方面需求,如API接口的安全性、可重复调用、稳定性、好定位问题等等,我们应该如何设计一个优雅的API接口呢?今天跟大家一起来聊聊设计API接口时,需要注意的一些地方。
1、签名
对外开放的API接口都会面临安全问题,例如伪装攻击、篡改攻击、重放攻击以及数据信息泄露的风险,所以很多时候我们需要对API接口做签名,利用API接口签名能方便的防范这些安全问题和风险。
签名就是接口请求方将"请求参数+时间戳+密钥"拼接成一个字符串,然后通过md5等hash算法,产生sign,然后在请求参数或请求头中,增加sign参数,传递给API接口。而API接口的网关服务,获取到sign值,然后用相同的"请求参数+时间戳+密钥"拼接成一个字符串,用相同的md5算法生成另外一个sign,对比两个sign值是否相等。如果两个sign相等,则认为是有效请求,API接口的网关服务会将给请求转发给相应的业务系统。如果两个sign不相等,则API接口的网关服务会直接返回签名错误。我们签名中为什么要加时间戳呢?这个是为了安全性考虑,可以验证客户端请求是否在有效时间内,从而避免接口被长时间地重复调用,增加了密钥没破解的可能性,我们要对每次请求都设置一个合理的过期时间,比如:10分钟。这样一次请求在10分钟内是有效的,超过10分钟,则API接口的网关服务会返回超过有效期的异常提示。
目前生成签名中的密钥有两种形式:
(1)第一种:是双方约定一个固定值privateKey。
(2)第二种:是API接口提供方给出AK/SK两个值,双方约定用SK作为签名中的密钥进行签名计算。AK接口调用方作为header中的accessKey传递给API接口提供方,这样API接口提供方获取到请求的AK后,查询出对应的SK,用相同的规则进行签名计算来生成新的sgin。
2、加密
有时候我们的API接口直接传递的是很重要的数据,比如:用户的银行卡号、转账金额、身份证号等,如果将这些参数直接明文,暴露到公网上是非常危险的事情。
为了防止提交到接口的明文泄密,可以对提交到接口的数据加密,目前使用比较多的是用BASE64加解密。
API接口的调用方在传递参数时,body中只有一个参数data,它就是BASE64之后的加密数据,API接口的网关服务,在接收到data数据后,根据双方事先预定的密钥、加密算法、加密次数等,进行解密,并且反序列化出参数数据。
加密数据的示例如下:
3、IP白名单
为了加强API接口的安全性,防止接口的签名或者加密被破解了,攻击者可以在自己的服务器上请求该接口,限制请求IP,增加IP白名单。只有在白名单中的IP地址,才能成功请求API接口,否则直接返回无访问权限。IP白名单也可以加在API网关服务上。但也要防止公司的内部应用服务器被攻破,不过这种情况也可以从内部服务器上发起API接口的请求。这时就需要增加web防火墙了,比如:ModSecurity等。
4、限流
如果API接口被第三方平台调用了,就意味着调用频率是没法控制的。在第三方平台调用你的API接口时,如果并发量太高,则会导致API服务不可用,接口可能就直接挂掉。所以必须要对API接口做限流,在我们实际工作中,可以通过nginx,redis或者gateway实现限流的功能。
限流方法有三种:
(1)对请求IP做限流:如同一个IP,在1分钟内,对API接口总请求次数,不能超过5000次。
(2)对请求接口做限流:如同一个IP,在1分钟内,对指定的API接口,请求次数不能超过1000次。
(3)对请求用户做限流:如同一个AK/SK用户,在1分钟内,对API接口总请求次数,不能超过5000次。
5、参数校验
对API接口做参数校验,比如:校验必填字段是否为空,校验字段类型、字段长度、枚举值等等,这样能拦截一些无效的请求。在新增数据时,如果字段长度超过了数据字段的最大长度,数据库会直接报错。但这种异常请求,我们尽量在API接口的前期要识别出来,没必要在数据库保存数据那一步,不然就浪费了系统资源。再比如有些金额字段,本来是正数,但用户不小心传入了负数,接口要是没做校验,就会导致一些没必要的损失。还有状态字段,要是不做校验,用户如果传入了系统中不存在的枚举值,就会导致保存的数据异常。
由上面所说的,做参数校验是很有必要的。在Java中校验数据使用最多的是hiberate的Validator框架,它里面包含了@Null、@NotEmpty、@Size、@Max、@Min等注解。用这些注解校验数据是非常方便的,当然有些日期字段和枚举字段,可能就需要通过自定义注解的方式来实现参数校验。
6、统一返回值
作为开发,我们项目的响应结果,需要统一标准的返回格式。如果返回值中有多种不同格式的返回数据,加上API接口出现不同的异常时,又返回不同的返回值结构,这样就会导致对接方很难理解,也非常不利于接口的维护。这个问题我们可以在设计API网关时解决。业务系统在出现异常时,抛出业务异常的RuntimeException,其中有个message字段定义异常信息。所有的API接口都必须经过API网关,API网关捕获该业务异常,然后转换成统一的异常结构返回,这样能统一返回值结构。列举了几个返回数据的示例,如下:
(1)正常返回数据是一种json格式:
(2)签名错误返回的json格式:
(3)没有数据权限返回的json格式:
7、统一封装异常
日常开发中,我们一般都需要对异常进行统一处理。比如在API接口中,需要访问数据库,但表不存在,或sql语句异常,就会直接把sql信息在API接口中直接返回,返回值中包含了异常堆栈信息、数据库信息、错误代码和行数等信息。如果直接把这些内容暴露给第三方平台,是件很不安全的事情。这会让一些不法分子,利用接口返回值中的信息,有可能会进行sq注入或直接脱库,这样就会对我们系统造成损失。所以我们需要对API接口中的异常做统一处理,把异常转换成如下:
code响应状态码是500,返回信息message是服务器内部异常,这样第三方平台就知道是API接口出现了内部异常问题,就会来找我们排查问题。我们就在内部的日志文件中,把堆栈信息、数据库信息、错误代码行数等信息,打印出来。我们在aatewav中对异常进行拦截,做统一封装,然后给第三方平台的是处理后没有敏感信息的
错误信息。
8、请求日志
在对第三方平台提供接口调用时,通常需要对接口调用情况进行记录以便问题追踪和排查,我们需要把API接口的请求url、请求参数、请求头、请求方式、响应数据和响应时间等都记录到日志文件中。最好加上traceld,它是用于串联某一次请求在系统中经过的所有路径。
当然有时请求日志不光是开发人员需要查看,第三方平台的用户也需要能查看接口的请求日志。这时就需要把日志落地到数据库,比如:mongodb或者elastic search,然后做一个U页面,给第三方平台的用户开通查看权限。这样他们就能在外网查看请求日志并定位到一部分问题。
9、幂等设计
API接口幂等设计是保证数据的唯一性,不允许有重复。如果第三方平台有bug,或者在做接口调用失败重试,请求接口多次,比如:在1秒内请求两次。第三方平台用相同的参数请求API接口多次,第一次请求数据库会新增数据,但第二次请求以后就不会新增数据,但也会返回成功。这样是不会产生错误数据。在我们的日常开发中,可以通过在数据库中增加唯一索引,或者在redis保存requestld和请求参来保证接口幂等性。
10、限制记录条数
对于批量接口,一定是要限制请求的记录条数。因为请求的数据太多,很容易造成API接口超时等问题,这会让API接口变得不稳定。通常情况下,建议一次请求中的参数,最多支持传入500条记录。如果用户传入多余500条记录,则接口直接给出提示。这个参数最好是做成可配置的,并且要事先跟第三方平台协商好,避免上线后产生不必要的问题。
11、压力测试
压力测试是上线之前都需要做的一个测试,它可以帮助我们发现系统中的瓶颈问题,减少发布到生产环境后出问题的几率:预估系统的承载能力,使我们能根据其做出一些应对措施,所以压力测试是一个非常重要的步骤。
之前虽说对API接口做了限流,但是实际上API接口是否能够达到限制的阀值,这是一个问号,如果不做压力测试,就会存在很大的风险。比如:你API接口限流1秒只允许50次请求,但实际API接口只能处理30次请求,这样你的API接口也会处理不过来。我们在工作中可以用jmeter或者apache benc对API接口做压力测试。
12、异步处理
一般的API接口的逻辑都是同步处理的,发送一个请求,需要等待返回,然后才能够发送下一个请求,有个等待过程;只有当前API执行完成后,才能继续执行下个API,这样耗时会非常长。为了提升API接口的性能,我们可以改成异步处理,在API接口中可以发送一条mq消息,然后直接返回成功。之后有个专门的mq消费者去异步消费该消息,做业务逻辑处理。
第三方平台有两种方式获取到直接异步处理的接口:
(1)第一种:回调第三方平台的接口,告知他们API接口的处理结果。
(2)第二种:第三方平台通过轮询调用我们另外一个查询状态的API接口,每隔一段时间查询一次状态,传入的参数是之前的那个API接口中的id集合。
13、数据脱敏
通常接口返回值中的一些敏感数据是要脱敏的,比如身份证号、手机号、银行账号等,如果这些信息通过API接口直接暴露到外网,是非常不安全的,很容易造成用户隐私数据泄露的问题。我们在返回的数据中,一般用*隐藏一部分数据,以手机号为例:186****0947。这样即便数据被泄露了,也只泄露了一部分,不法分子拿到这份数据也没什么用。
14、完整的接口文档
在双方做接口对接时,一份完整的API接口文档可以减少很多沟通成本,会少走很多弯路。
API接口文档中需要包含以下几个部分:
(1)接口名称;
(2)简要描述;
(3)请求的URL;
(4)请求方式(GET/POST等):
(5)请求参数(参数名、是否必选、参数类型、说明):
(6)返回示例;
(7)返回参数说明(参数名、类型、说明):
(8)备注及责任人。
接口文档中尽量能够统一接口和字段名称的命名风格,比如:
(1)都用驼峰标识命名:
(2)统一字段的类型和长度,如id字段用Long类型,长度规定20;status字段用int类型,长度固定2等;
(3)统一时间格式字段,如time用String类型,格式为:yyy-MM_dd HH:mm:ss;
(4)接口文档中写明AK/SK和域名,找某某单独提供等。