大数跨境

开放API设计方案

开放API设计方案 wordpress知识
2026-10-05
10

对外开放API安全架构设计与实现

对外开放API面向第三方调用方,主要面临抓包、重放攻击及恶意刷量等安全风险。构建高可用的开放平台需聚焦以下核心关键点:

  • 签名鉴权:确保请求来源合法且未被篡改。
  • 防重放:防止恶意用户截获请求后重复发送。
  • 限流机制:保护服务端资源,防止恶意刷量。
  • 版本兼容:通过路由隔离实现平滑升级。
  • 统一返回格式:降低对接成本,规范交互标准。
  • 完整日志:便于问题排查与安全审计。

整体设计思路

采用版本化路由策略隔离对外接口,例如 /api/open/v1/* 和 /api/open/v2/*,确保新旧版本互不干扰。

请求执行链路如下:

  1. HTTPS 加密传输
  2. 进入对外 API 路由分组
  3. 经过 OpenApi 中间件(执行签名校验、时间戳验证、Nonce 防重放、限流控制及日志记录)
  4. 分发至对应版本的控制器
  5. 返回统一 JSON 格式数据

核心原则:将鉴权、安全校验及限流逻辑全部下沉至中间件处理,控制器仅负责核心业务逻辑,实现关注点分离。

公共参数与签名机制

第三方发起请求时必须携带以下公共参数:

  • app_id:分配给第三方的应用唯一标识。
  • timestamp:秒级时间戳,服务端校验其有效期(通常为 5 分钟),用于防重放。
  • nonce:随机字符串,确保单次请求的唯一性。
  • sign:SHA256 签名值。

签名生成规则

  1. 剔除参数中的 sign 字段,并过滤掉空值参数。
  2. 将剩余参数按键名进行 ASCII 升序排序。
  3. 按照 key1=val1key2=val2... 的格式拼接字符串,末尾追加 app_secret。
  4. 对拼接后的字符串进行 SHA256 加密,并转换为大写,即为最终签名。

统一返回格式

所有接口响应均遵循以下 JSON 结构,包含状态码、消息、数据体及请求追踪 ID:

{
    "code": 0,
    "msg": "ok",
    "data": {},
    "request_id": "xxxxxx"
}

数据库设计

创建 access_api_app 表用于管理第三方接入账号,存储应用 ID、密钥、IP 白名单及配额限制等信息。

CREATE TABLE `access_api_app` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `app_id` varchar(64) NOT NULL COMMENT '应用ID',
  `app_secret` varchar(128) NOT NULL COMMENT '密钥,加密存储',
  `app_name` varchar(100) NOT NULL COMMENT '合作方名称',
  `ip_white` text COMMENT 'IP白名单,逗号分隔,空则不限制',
  `qps_limit` int NOT NULL DEFAULT 10 COMMENT '每秒最大请求',
  `day_limit` int NOT NULL DEFAULT 10000 COMMENT '每日最大调用次数',
  `status` tinyint NOT NULL DEFAULT 1 COMMENT '1启用 0禁用',
  `create_time` int NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_appid` (`app_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开放api第三方接入账号';

代码实现方案

1. 路由定义 (route/api.php)

仅维护对外开放接口的路由,并通过中间件绑定安全策略。

<?php
use think\facade\Route;

// 开放API v1版本
Route::group('api/open/v1', function (){
    Route::post('demo/test', 'OpenV1/Demo/test');
})->middleware(\app\middleware\OpenApi::class);

// 开放API v2版本,涉及破坏性改动时新建版本
Route::group('api/open/v2', function (){
    Route::post('demo/test', 'OpenV2/Demo/test');
})->middleware(\app\middleware\OpenApi::class);

2. 公共工具类 (app/common/OpenApiHelper.php)

封装签名生成、统一响应及请求 ID 生成方法。

<?php
namespace app\common;

class OpenApiHelper
{
    /**
     * 生成签名
     */
    public static function makeSign(array $params, string $appSecret): string
    {
        unset($params['sign']);
        // 过滤空值
        $params = array_filter($params, function($v){
            return $v !== '' && $v !== null;
        });
        ksort($params, SORT_ASCII);
        $str = '';
        foreach ($params as $k => $v){
            $str .= $k . $v;
        }
        $str .= $appSecret;
        return strtoupper(hash('sha256', $str));
    }

    /**
     * 统一对外返回
     */
    public static function response(int $code=0, string $msg='ok', $data=[], string $requestId='')
    {
        return json([
            'code' => $code,
            'msg' => $msg,
            'data' => $data,
            'request_id' => $requestId
        ]);
    }

    /**
     * 生成追踪 request_id
     */
    public static function genRequestId(): string
    {
        return md5(uniqid(microtime(true), true).rand(1000, 9999));
    }
}

3. 核心中间件 (app/middleware/OpenApi.php)

中间件承担主要安全职责:校验 App ID、IP 白名单、时间戳、Nonce 防重放、签名验证、QPS 限流及日调用限额,并记录请求日志。

<?php
namespace app\middleware;
use app\common\OpenApiHelper;
use think\facade\Db;
use think\facade\Redis;

class OpenApi
{
    public function handle($request, \Closure $next)
    {
        $requestId = OpenApiHelper::genRequestId();
        $params = $request->isJson() ? $request->post() : $request->param();

        // 1. 校验公共参数
        if(empty($params['app_id']) || empty($params['timestamp']) || empty($params['nonce']) || empty($params['sign'])){
            return OpenApiHelper::response(40001, '缺少公共参数', [], $requestId);
        }

        $appId = $params['app_id'];
        $timestamp = $params['timestamp'];
        $nonce = $params['nonce'];
        $sign = $params['sign'];

        // 2. 时间戳校验 (允许正负5分钟误差)
        $now = time();
        if(abs($now - $timestamp) > 300){
            return OpenApiHelper::response(40002, '时间戳过期', [], $requestId);
        }

        // 3. Nonce 防重放 (5分钟内同一 nonce 拒绝重复)
        $nonceKey = "openapi:nonce:{$appId}:{$nonce}";
        if(Redis::exists($nonceKey)){
            return OpenApiHelper::response(40003, '重复请求', [], $requestId);
        }
        Redis::setex($nonceKey, 300, 1);

        // 4. 查询第三方接入账号信息
        $appInfo = Db::name('access_api_app')->where('app_id', $appId)->find();
        if(!$appInfo || $appInfo['status'] != 1){
            return OpenApiHelper::response(40004, 'app_id无效或已禁用', [], $requestId);
        }

        // 5. IP 白名单校验
        if(!empty($appInfo['ip_white'])){
            $allowIps = explode(',', $appInfo['ip_white']);
            $clientIp = $request->ip();
            if(!in_array($clientIp, $allowIps)){
                return OpenApiHelper::response(40005, 'IP不在白名单', [], $requestId);
            }
        }

        // 6. 签名校验
        $realSign = OpenApiHelper::makeSign($params, $appInfo['app_secret']);
        if($realSign !== $sign){
            return OpenApiHelper::response(40006, '签名错误', [], $requestId);
        }

        // 7. QPS 限流
        $qpsKey = "openapi:qps:{$appId}";
        $qps = Redis::incr($qpsKey);
        if($qps === 1){
            Redis::expire($qpsKey, 1);
        }
        if($qps > $appInfo['qps_limit']){
            return OpenApiHelper::response(40007, '调用频率超限', [], $requestId);
        }

        // 8. 每日调用次数限制
        $dayKey = "openapi:day:{$appId}:" . date('Ymd');
        $dayCount = Redis::incr($dayKey);
        if($dayCount === 1){
            Redis::expire($dayKey, 86400);
        }
        if($dayCount > $appInfo['day_limit']){
            return OpenApiHelper::response(40008, '今日调用次数已用尽', [], $requestId);
        }

        // 注入信息供控制器使用
        $request->open_app = $appInfo;
        $request->request_id = $requestId;

        // 执行业务逻辑
        $response = $next($request);
        
        // 此处可增加对外接口请求日志入库,注意敏感参数脱敏
        
        return $response;
    }
}

4. 业务控制器 (app/controller/OpenV1/Demo.php)

控制器专注于业务处理,无需关心安全校验细节。

<?php
namespace app\controller\OpenV1;
use app\common\OpenApiHelper;
use think\Request;

class Demo
{
    public function test(Request $request)
    {
        $reqId = $request->request_id;
        $appInfo = $request->open_app;
        
        // 获取第三方提交的业务参数
        $param = $request->post();
        
        // 执行具体业务逻辑......
        
        return OpenApiHelper::response(0, 'ok', [
            'info' => 'v1版本接口返回数据',
            'app_id' => $appInfo['app_id']
        ], $reqId);
    }
}

开发避坑指南

  • 密钥安全:AppSecret 严禁明文输出或明文存储在数据库中,必须进行加密处理。
  • 错误屏蔽:禁止将 PHP 异常堆栈、SQL 错误详情直接返回给第三方,应统一转换为友好的错误提示。
  • 强制 HTTPS:所有对外接口必须通过 HTTPS 访问,禁止使用 HTTP,以防数据在传输过程中被窃听或篡改。
  • 双重防重放:必须同时使用 timestamp 时间戳和 nonce 随机串,单一机制无法有效抵御抓包重放攻击。
  • 路由收敛:所有对外接口必须统一通过 /api/open/* 路由分组暴露,严禁随意暴露内部业务控制器。
  • 全链路追踪:每一次请求响应都必须包含唯一的 request_id,作为对接方排查问题的唯一凭证。
【声明】内容源于网络
0
0
wordpress知识
各类跨境出海行业相关资讯
内容 371
粉丝 0
wordpress知识 各类跨境出海行业相关资讯
总阅读11.8k
粉丝0
内容371