三方接口调用方案设计

目录

设计方案概述

权限划分

签名规则

服务端安全性 

    单个接口针对ip 限流

    记录接口请求日志

     敏感数据脱敏

    幂等性问题(重复更新或者新增)

响应状态码规范

统一响应数据格式


在为第三方系统提供接口的时候,肯定要考虑接口数据的安全问题,比如数据是否被篡改,数据是否已经过时,数据是否可以重复提交等问题。

在设计三方接口调用的方案时候,需要考虑到安全性和可用性。以下是一种设计方案概述,其中包括使用API 秘钥(Access Key / Secret Key) 进行身份验证和设置回调地址

设计方案概述

    1、API密钥生成:  为每个三方应用生成唯一的API 密钥对(AK /SK), 其中AK 用于标识应用,SK应用进行签名和加密。

          AK: Access Key Id 用于标识用户

         SK: Secret Access Key 是用于用于加密认真字符串和用来验证字符串的秘钥,其中SK 必须加密 

 通过使用Access Key Id/ Secret Access Key 加密方凡来验证某个请求的发送者身份

     2、接口鉴权: 在进行接口调用的时候,客户端需要使用AK和请求参数生成签名,并将其放入请求头或参数中以进行身份验证。

   3、回调地址设置:  三方应用提供回调地址,用于接收异步同步和回调结果

   4、接口API设计:  设计接口的URL、HTTP方法、请求参数、响应格式等细节。

 

权限划分

    appID : 应用的唯一标识

          用来标识你的开发者账号的,即: 用户id,可以在数据库添加索引,方便快速查找,同一个appId 可以对应多个 appKey + appSecret 达到权限的

     appKey 公钥(相当于账号)

      公开的,调用服务所需要的秘钥,是用户的身份认证标识,用于调用凭条可用服务,可以简单理解是账号

   appSecret: 私钥(相当于密码)

       签名的秘钥,是跟appKey配套使用的,可以简单理解是密码

token: 令牌(过期失效)

使用方法

    ① 向第三方服务器请求授权的时候,带上appKey 和 appSecret (需要存在服务端)

    ② 第三方服务器验证的appKey 和appSecret 在数据库、缓存中有没有记录

   ③ 如果有,生成一串唯一的字符串(token) ,返回给服务器,服务器再返回给客户端。

   ④ 后续客户端每次请求都需要带上token 令牌

 

为什么 要一样appKey+ appSecret 这种成对出现的机制呢?

      因为要加密,通常在首次验证(类似登录场景), 用appKey(标记要申请的权限有哪些) + appSecret (密码,表示你真的拥有这个权限)来申请一个token, 就是我们经常用到的 accessToken (通常拥有失效时间), 后续的每次请求都需要提供 accessToken 表示验证权限通过。

现在有了统一的appId,此时如果针对同一个业务要划分不同的权限,比如,同一功能,某些场景需要只读权限,某些场景需要读写权限,这样提供了一个appId 和对应的秘钥appSecret  就没有办法满足需求,此时就需要根据权限进行账号分配,通常用 appKey 和 appSecret 

可以生成两对appKey 和 appSecret , 一个用于删除,一个用于读写,达到权限的细粒度划分,如 appKey1 + appSecret1 只有删除权限,但是 appKey2 + appSecret2 有读写权限,这样你就可以把对应的权限发放给不同的开发者。其中权限的配置都是跟 appKey 做关联的,appKey 也需要添加数据库索引,方便快速查找。

    简化场景:

          第一种场景: 通常用于开放性接口,像地图API,会剩余app_id 和app_key ,此时相当于三者相等,合二为一,appId = appKey = appSecret  ,这种模式下,带上 appId 的目的仅仅是统计某个拥挤调用接口的次数而已。

         第二种场景,当每一个用户有且仅有一套权限配置,可以去掉appKey ,直接将 appId = appKey ,每个用户分配一个 appId + appSecret 就够了。

         也可以采用签名(signature) 方式: 当调用三方服务提供的方法发起请求的时候,带上(appKey 、时间戳 timeStamp 、随机数nonce 、签名sigj), 其中 签名sign 可以使用 AppSecret + 时间戳 + 随机数  使用 sha1 、MD5生成, 服务提供方后收到后,生成本地签名和收到的签名比对,如果一致,校验成功。

     

签名规则

     1、分配appId(开发者标识) 和 appSecret (密钥) 给不同的调用方

        可以直接通过平台线上申请,也可以直接线下直接颁发,appId 是全局唯一的,每个appId将对应一个客户,密钥appSecret 需要高度保密

    2、加入 timeStamp(时间戳),以服务端当时时间为准,单位为ms, 5分钟内数据有效 

        时间戳的目的就是为了减轻DOS攻击,防止请求被拦截后一直尝试请求接口,服务端设置时间阈值,如果服务器时间 - 请求时间戳 超过阈值,表示签名超时,接口调用失败。

  3、加入临时流水号nonce ,至少为10位,有效期内放置重复提交。

     随机值nonce 主要是为了增加签名sign多边形,也可以保护接口的幂等性,相邻的两次请求nonce 不允许重复,如果重复则认为是重复提交,接口调用失败。

       ① 针对查询接口,流水号只用于日志落地,便于后期日志核查。

       ② 针对办理类接口需要校验流水号在有效期的唯一性,以避免重复请求。

   通过在接口签名请求参数加上 时间戳 timeStamp +随机数 可以防止重放攻击。

 

1、时间戳(timeStamp)

 以服务端当前时间为准,服务端要求客户端发过来的时间戳,必须是最近60秒内(假设值,自己定义) 的

  这样,即使这个请求即使被截取了,也只能在60s内进行重放攻击。

2、随机数(nonce)

     但是,即使设置了时间戳,攻击者还有60s的攻击时间呢!

     所以我们需要再客户端请求中再加上一个随机数(中间黑客不可能自己随机修改参数,因为有参数签名校验呢) ,服务端会对一分钟内请求的随机数进行检查,如果有两个相同的,基本可以判定为重放攻击。

     因为正常情况下,在短时间内(比如60s)连续生成两个相同nonce 的情况几乎为0。

     服务端"第一次"在接收到这个nonce 的时间做下面行为。

      1、去redis 中查找是否有key 为nonce 的数据 

      2、如果没有,则创建这个key, 把这个key 失效时间和验证timestamp 失效时间一致,比如是60s 

      3、如果有,说明这个key在60s内已经被使用了,那么这个请求就可以判断为重放请求。

     4、加入签名字段sign,获取调用方传递的签名信息。

 

通过在接口签名请求参数加上 时间戳appId + sign 解决身份验证和防止 “参数篡改”

       1、请求携带参数appId 和Sign ,只有拥有合法的身份appId 和正确的签名sign 才能放行,这样就解决了身份验证和参数篡改问题。

        2、即使请求参数被劫持,由于获取不到appSecret (仅作为本地加密使用,不参与网络传输),也无法伪造合法请求。

以上字段放在请求头中。 其中nonce 应该是一个随机的、唯一的字符串,可以使用UUID或者其他随机字符串生成算法来创建。TimeStamp 表示请求的时间戳,通常使用标准的Unix 时间戳格式(以秒为单位)

总结:  ①  提供appId(用户账户id)  和 appSecret (加密秘钥,用于参数加密后服务端的二次校验)

          ② 参数中使用timeStamp(时间戳,保证请求的实时性)  和nonce (保证在有效期内放置被重放).其中nonce 一般有uuid 或者随机字符串生成,服务端接收到请求后,记录到缓存中,其缓存过期时间为有效期长短,在有效期内如果是重复的则认为是重放请求。

 

服务端安全性 

    单个接口针对ip 限流

     使用redis进行接口调用次数统计,ip + 接口地址作为key,方位次数作为value ,每次请求value + 1 ,设置过期时长来限制接口的调用频率。

    记录接口请求日志

 记录请求日志,快速定位异常请求位置,排查问题原因,如使用aop 来全局处理接口请求

     敏感数据脱敏

在接口调用过程中,可以会涉及到订单号等敏感数据,这类数据通常需要脱敏处理。

最常用的方式就是加密,加密方式使用安全性比较高的RSA 非对称加密。非堆成加密算法有两个秘钥,这两个秘钥完全不同单又完全屁屁,只有使用匹配的一对公钥和私钥,才能完成对明文的加密和解密过程。

    幂等性问题(重复更新或者新增)

    ①提供一个生成随机数的接口,随机数全局唯一,调用接口的时候带入随机数。

    ② 第一次调用,业务处理成功后,将随机数作为key,操作结果作为value,存入redis ,同时设置过期时长。

    ③第二次调用,查询redis,如果key存在,则证明重复提交,直接返回错误。

 

响应状态码规范

 状态码设计参考如下: 

public enum CodeEnum {
    SUCCESS(200,"处理成功"),
    ERROR_PATH(404,"请求地址错误"),
    ERROR_SERVER(505,"服务器内部发生错误");

    private int code;

    private String message;

    
    CodeEnum(int code, String message) {
        this.code = code;
        this.message = message ;
    
    }
 
    private void setCode(int code) {
       this.code = code;
 
    }

    private int getCode() {

        return this.code;
    }


    private void setMessage(String message) {
       this.message = message;

    } 
 
    private String getMessage() {

       return message;
    }


}

统一响应数据格式

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

     

public class Result implements Serializable {

     private static final long serialVersionUID = 111L;

     private int code;
 
     private String message;
 
     private Object data = null;

     private int getCode() {

        return code;
     }

     private void setCode(int code) {
        this.code = code;
     }
  
     public String getMessage() {
        return message;
     }

     public void setMessage(String message) {
         this.message = message;
     } 


     public Result fillData(Object data) {
        this.setCode(CodeEnum.SUCCESS.getCode());
        this.setMessage(CodeEnum.SUCCESS.getMessage());
        this.data = data;
        return this;
     }


}

 

    

### Java 中调用三方接口时的回滚解决方案 当涉及到多个子系统之间的交互,尤其是跨服务或微服务架构中的操作,确保一致性是一个复杂的问题。对于Java应用程序而言,在调用三方接口过程中遇到错误并需要执行回滚的情况下,可以采用多种策略和技术来处理这个问题。 #### 使用本地消息表模式 一种常见的做法是在数据库中创建一张专门用于存储待确认的消息记录表。每当发起一次对外部系统的请求前,先将相关信息写入此表;一旦外部调用失败,则依据这些预先保存的数据来进行补偿性的逆向操作或是重试逻辑[^3]。 ```sql CREATE TABLE IF NOT EXISTS message_log ( id BIGINT AUTO_INCREMENT PRIMARY KEY, service_name VARCHAR(255), operation_type ENUM('INSERT', 'UPDATE', 'DELETE'), payload TEXT, status ENUM('PENDING', 'SUCCESSFUL', 'FAILED') DEFAULT 'PENDING', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); ``` #### 实现基于Saga模式的服务编排 另一种更为结构化的方法是引入Saga模式——这是一种长运行事务管理方式,它允许一系列独立但相互关联的操作按顺序依次完成。每个单独的动作都设计成幂等(idempotent),即多次执行相同命令的结果不变。如果某个环节出现问题,后续步骤会被跳过,并启动相应的恢复程序去撤销之前已完成的部分工作。 ```java public class SagaOrchestrator { private final List<SagaStep> steps; public void execute() throws Exception { try { for (var step : steps) { step.apply(); } } catch (Exception e) { rollback(steps.subList(0, steps.indexOf(step))); throw e; // Re-throw exception after rollback. } } private void rollback(List<SagaStep> executedSteps) { Collections.reverse(executedSteps); for (var step : executedSteps) { step.compensate(); } } } ``` #### 利用分布式事务协调器(TCC/XA) 对于更严格的一致性需求场景下,还可以考虑借助成熟的分布式事务框架如TCC(Try Confirm Cancel)、XA协议等工具。这类技术能够提供更强有力的支持以保证不同资源间的同步更新行为。不过值得注意的是它们往往伴随着较高的性能开销以及额外的学习成本[^1]。 通过上述三种途径之一或者组合应用,可以在很大程度上缓解甚至彻底解决问题所描述的现象,从而提高整个业务流程的安全性和可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值