buguge - Keep it simple,stupid

知识就是力量,但更重要的,是运用知识的能力why buguge?

导航

开发一份API接口,需要注意这些,看你做到了几项

在实际工作中,我们需要经常跟外部三方系统打交道,可能会提供API接口给外部三方系统调用。

API接口通常通过WebController来实现。如果设计一个优雅的API接口,能够满足安全性、稳定性、易维护等多方面需求呢?

下面几项,看你做到了哪些。

1. 数字签名

为了防止API接口中的数据被篡改,我们需要对接口做签名。签名密钥由请求方和提供方互存,不参与网络传输。

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

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

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

如果两个sign相等,则认为是有效请求,API接口的网关服务会将给请求转发给相应的业务系统;如果两个sign不相等,则API接口的网关服务会直接返回签名错误。

BTW,提个问题:签名中为什么要加时间戳?

答:为了安全性考虑。一来,通过不同的时间戳,可以有效减少签名相同的概率。二来,API接口的网关服务可以校验时间戳,与服务器当前时间是否吻合(实际生产中会容许几秒的差异),不吻合就直接返回非法请求。

目前生成数字签名的密钥有3种形式:

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

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

第3种是使用非对称加密算法生成签名,常见的是RSA。RSA包含一对公私钥,其中一方保留自己的私钥,并将公钥提供给另一方。调用方发起API请求时通过私钥加签,另一方则通过其公钥验签。

我们可以使用在线工具生成密钥对:https://tools.ytdevops.com/rsa-key-pair-generator

2. 敏感数据处理

有些时候,我们的API接口会直接传递非常重要的数据,比如:用户的登录密码、银行卡号、手机号、用户身份证等,将这些参数直接明文暴露到公网上是非常危险的事情,很容易造成用户隐私数据泄露。

这些用户隐私数据被称作敏感数据。那么,针对敏感数据,API设计上我们怎么做呢?

一句话:对于交易类接口,涉及敏感数据的,做加密处理。对于非交易类接口,涉及敏感数据的,考虑做脱敏处理。当然,还有一个原则是,非需不返回用户隐私数据

敏感数据加密

数据加密通常使用对称加密算法。如AES、DES、3DES。双方互存加密秘钥encryptKey。一方利用encryptKey加密,另一方利用encryptKey解密。

当然,也可以对数据进行RSA非对称加密。调用方利用API提供方的公钥对数据加密,API提供方则利用自己的私钥对数据解密。

需要指出的是,无论是RSA加密还是RSA签名,服务提供方的公私钥只有一份,而不同的外部三方系统应持有不同的公私钥。

另外,对于用户敏感数据,服务提供方在合法合规的前提下,应做加密存储,而不是直接明文存储。

敏感数据脱敏

为保护用户隐私,用户的敏感信息经常要做脱敏处理。尤其在支付系统或金融系统,数据安全是第一要务,数据的脱敏处理更是必选项。

数据脱敏,指的是将用户敏感数据的一部分字符用特殊字符作为掩码来表示。

  • 【身份证号脱敏示例】110115201406180712 脱敏后:110115********0712
  • 【银行卡号脱敏示例】9558820200019833888 脱敏后:955882*********3888
  • 【手机号脱敏示例】18810754438 脱敏后:188******38

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

算法实现原理很简单,就是保留头尾字符,把中间的部分用特殊字符如星号“*”作为掩码来表示。

🥬 BTW,用数据加密 还是 数据脱敏?-- 数据加密和数据脱敏适用场景,也许你未必清楚

数据加密和数据脱敏是数据安全领域中常用的保护敏感数据的方法。理解这句话有三点:

  • 数据加密和数据脱敏属于数据安全领域(本文主要指信息系统的用户数据安全,参见《中华人民共和国个人信息保护法 》)
  • 针对的是用户隐私数据的保护,用户隐私数据属于敏感数据。这些数据包括用户身份证号、手机号、企业资质号码、用户身份证图片、企业资质图片、用户银行卡号、企业银行账号、用户登录口令,甚至是用户真实姓名
  • 通过保护数据安全和隐私,降低数据泄露的风险

不过,两者在数据安全领域扮演着不同但重要的角色,它们在保护数据安全和隐私方面有着不同的侧重和应用场景

数据加密 主要关注数据的机密性,通过加密算法将数据转换为不可读的形式,以防止未经授权的访问者访问或篡改数据。数据加密常用于数据传输存储等场景,确保数据在传输和存储过程中的安全性。

数据脱敏 则主要关注隐藏敏感数据的真实性,以保护数据的隐私和遵守法规要求。

首先,数据脱敏用于非生产环境下,如开发、测试环境中使用数据时。

其次,在法律范围内不允许直接使用明文数据的系统中,使用数据脱敏。如用户身份证号、手机号这些隐私数据,在许多企业应用系统中,是不允许直接存储和使用的,应采用数据脱敏。

再次,在一些数据传输场景中,也要通过数据脱敏技术来隐藏敏感数据。如网络接口请求中,如果请求方无权获取明文用户的身份证号、银行卡号、企业开户银行等数据,则接口提供方要进行数据脱敏处理后返回。

本文主要谈信息系统的数据安全。BTW,日常工作中,涉及到用户隐私数据的文本、图片或截图,我们亦不能通过IM聊天工具直接相互发送,也不能在内网WIKI、网络空间进行共享。应进行数据脱敏处理,常用的方式是打码、加*号。

综合来看,数据加密和数据脱敏在数据安全领域扮演着不同但重要的角色,我们要根据具体需求和场景选择合适的方法来保护数据安全和隐私。

3 统一请求、统一返回

外放API通常是POST接口,数据通过json格式传输。

首先,统一请求/返回结构。在WebController网关里,定义请求模型,例如 ApiRequest { merId, version, sign, timestamp, data},对于聚合接口,可能要定义一个action来指定具体的请求,如action="pay"表示支付,action="payQuery"表示支付结果查询;定义响应模型,常见的例如 ApiResult {code, message, data}。

其次,对于响应,全局统一正常的响应码,如对于所有接口来说,返回code=200 表示正常响应。 然后,对于错误响应,要枚举出来各种错误码,并明确错误描述。

正常返回示例:

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

签名错误返回:

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

没有数据权限返回:

{
    "code":403,
    "message":"没有权限",
    "data":null
}

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

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

4. API接口参数定义的考究

在设计 API 接口时,参数定义是至关重要的,它可以影响接口的易用性、可维护性,甚至是性能。以下是一些在定义 API 接口参数时需要考虑的重要方面。

接口文档中最好能够统一接口和参数名称的命名风格,推荐用驼峰式命名。

参数名应明确,便于理解。这同时考验程序员的英语水平。例如:回调地址,可以是 callback_url,如果是 call_black_url 就容易闹笑话了。

参数名统一:例如,对于订单号,所有API统一使用tradeNo,切不可在不同的API中同时出现orderNo、tradeNo、transNo、platOrderNo等。再例如,对于商户编码,统一使用merId。

参数值统一:如对于时间参数,都使用 yyyy-MM-dd HH:mm:ss

参数类型应明确。确定每个参数的数据类型,如整数、字符串、布尔值、集合、子类型等。

接口地址中可以加一个版本号version,比如:v1/invoice/apply,这样以后接口有很大的变动,可以非常方便升级版本。

统一参数的类型和长度,比如:id用Long类型,长度规定20。amount用Long,单位用分,长度20。status用int类型,长度固定2等。

5. 校验请求参数

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

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

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

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

例如,用户手机号,不符合手机号正则,则直接返回错误。

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

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

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

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

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

6. 限制网络传输的数据量

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

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

通常情况下,建议一次请求中的参数,最多支持传入500条记录。如果用户传入多于500条记录,则接口直接给出提示。

同样,返回数据时,也不宜返回满足条件的所有数据。应该做分页处理,例如每页返回500条记录,并返回是否结束的标记。

再者,对于图片数据,如果传输经过Base64或Hex编码的字符串,应控制字符串的大小。例如在保证图片质量的前提下,要求调用侧压缩图片控制在1M内。当然,也可以考虑分块传输,即将数据分成多个小块进行传输,并在接收端重新组合。

7. 幂等设计

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

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

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

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

8. 限流

如果你的API接口被外部三方系统调用了,这就意味着着,调用频率是没法控制的。

你永远不知道你的用户(系统)会怎样使用你的接口,可能他们会因为错误地返回结果而不停地重试接口,我们要能够对频繁请求进行限制。

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

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

限流方法有三种:

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

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

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

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

9. 统一封装异常

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

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

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

如果直接把这些内容暴露给外部三方系统,是很危险的事情。

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

因此非常有必要对API接口中的异常做统一处理,在内部的log文件中,把堆栈信息、数据库信息、错误代码行数等信息,打印出来。并把异常转换成这样进行返回:

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

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

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

10. 记录请求处理日志

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

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

最好有traceId,可以通过它串联整个请求的日志,过滤多余的日志。分布式系统中,可以借助skywalking的TID组件来追踪一个请求从起始到结束经过的所有服务和组件,以便进行性能分析、故障排查等操作。

11. 异步处理

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

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

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

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

直接异步处理的接口,外部三方系统有两种方式获取到。

第一种方式是:我们系统业务执行完成后,主动回调外部三方系统的接口,告知他们API接口的处理结果,很多支付接口就是这么玩的。

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

12. 完整的API接口文档

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

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

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

复杂的业务流程通常需要暴露多个API接口,这时,一份明确的时序图有如锦上添花。

参数的顺序应该有逻辑性,使得使用者能够轻松理解和记忆。

区分必选参数和可选参数,明确指明哪些参数是必需的,哪些是可选的。对于可选参数,考虑设置默认值,以减少用户必须提供的参数数量。

接口文档中写明AK/SK和域名,找某某(如技术支持)单独提供等。

13. 开发者对接SDK

我们在对接银行通道或微信、支付宝等三方支付平台时,会经常使用它们提供的SDK开发包。

提供一份开发者对接 SDK 可以帮助对方技术开发人员更快速、高效地开发应用程序,降低开发成本和对接难度,提高用户体验。

SDK包中,应包含下面几个package。

  • 基础工具,加解密工具、数字签名工具、HttpUtil工具,等。
  • 公共package,如 枚举、常量、自定义异常、配置。
  • Model包,包含各种数据模型或实体类,用于表示和处理 SDK 所涉及的数据结构。
  • 接口对接。完整的API对接代码。当然,也可以对通用逻辑进行抽象封装。
  • test包,包含单元测试、集成测试等测试类。

代码中,应包含必要的javadoc,注明代码的用途,或可参考的内容。

应谨慎编写SDK中的代码,不可随意。SDK包 有可能被对接方开发者直接引入到项目中,因此在代码质量、http连接工具等方面,进行评审和测试。

EOF

感觉这个结束有点唐突~
昨天品读了园子里 @苏三说技术 的博文《瞧瞧别人的Controller,那叫一个优雅!》,有感而发,摘取了一些内容,然后加上自己的一些沉淀,撰写此文,纯为分享园友们!
数据脱敏处理算法

posted on 2024-11-20 10:16  buguge  阅读(107)  评论(0编辑  收藏  举报