• 如何给API签名


    前言

    有时候为了保护API,需要用到 API 签名,使用 API 签名的好处:

    • 让API只能被特定的人访问
    • 防止别人抓包拿到请求参数,通过篡改参数发起新的请求

    客户端过程

    1. 给API调用者分配一个app_idapp_secretapp_secret调用者和服务端各保存一份,不对外泄露,app_id需要在调用 API 时作为参数传递。
    2. 除了app_id参数以外,另外增加noncetimestamp参数。timestamp是时间戳,nonce是一个随机字符串,每次调用API时都要重新生成,且需要保证唯一性。这两个参数主要用来防止重复请求。
    3. 将参数列表按字典升序排列,然后在末尾添加上app_secret参数,将参数列表拼接成一个字符串,对字符串做md5加密,得到signature签名,最后将签名也传递给API。

    将参数列表拼接成字符串,可以有多种实现方法,在本文中我们使用这种方案:
    1)将参数列表进行排序,按key字典升序排列
    2)在末尾添加上app_secret参数
    3)按key1=value1&key2=value2&key3=value3的格式,将参数列表拼接成一个字符串,其中value值要经过url编码处理
    4)拼接完成后,做md5运算,生成散列值,此值便是签名

    服务端过程

    1. 收到请求后,按照app_id参数查询到对应的app_secret
    2. 判断timestamp参数是否已过期
    3. 判断nonce参数是否已经被使用
    4. 剔除sign参数,将其它参数按照约定的签名计算方法,生成一个签名A,将签名Asign参数进行对比,如果一致则通过
    5. nonce参数标记为已使用(可以使用redis来存储,并设置过期时间)

    注:
    1)时间戳的超时时间不能设置得太小,需要考虑服务端和客户端时间可能会有时差的情况,推荐设置为5分钟
    2)对于nonce参数,redis key的过期时间一定要大于时间戳的过期时间,比如时间戳过期时间是5分钟,那么redis key的过期时间就要 >= 5分钟

    PHP代码示例

    client.php
    
    
    function genSignature(array $signArr, $appSecret): string
    {
        ksort($signArr, SORT_STRING);
        $signArr['app_secret'] = $appSecret;
    
        $signStr = http_build_query($signArr);
        return md5($signStr);
    }
    
    function buildParams(array $params): array
    {
        $appId = 'xxxx';
        $appSecret = 'yyyy';
    
        $params['app_id'] = $appId;
        $params['nonce'] = uniqid('', true);
        $params['timestamp'] = time();
        $params['sign'] = genSignature($params, $appSecret);
    
        return $params;
    }
    
    $params = [
        'username' => '小明',
        'gender' => '男',
    ];
    var_dump(buildParams($params));
    
    // TODO:使用生成的参数调用API接口
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    server.php
    
    
    function signatureVerify(array $signArr, $appSecret): string
    {
        unset($signArr['sign']);
    
        ksort($signArr, SORT_STRING);
        $signArr['app_secret'] = $appSecret;
    
        $signStr = http_build_query($signArr);
        return md5($signStr);
    }
    
    $params = $_GET;
    
    $appId = $params['app_id'];
    // TODO:根据app_id参数查找对应的app_secret
    $appSecret = 'yyyy';
    
    $mySign = signatureVerify($params, $appSecret);
    if ($mySign === $params['sign']) {
        echo '签名验证成功';
    } else {
        echo '签名验证失败';
    }
    
    // TODO:检查timestamp是否已过期
    // TODO:检查nonce是否已被使用过
    // TODO:将nonce存储到redis,并设置key的过期时间
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    关于app_secret的安全性

    在上述整个过程中,最重要的是保证app_secret不外泄,如果app_secret外泄了,其他人就可以用它来生成签名。

    但真实情况下,我们很难保证app_secret不外泄,如果客户端是server端还好说,但如果客户端是浏览器的话,由于网页代码都是可以被看到的,所以只要查看一遍网页的js代码,就可以找到app_secret

    如果客户端是安卓APP,别人也可以通过拆解apk安装包,破解拿到app_secret

    面对这种情况也没有完美的方法,毕竟源代码都被别人看到了,还能有啥安全可言。只能通过js代码混淆、apk包安全加固等方法来提高别人找到app_secret的难度。

    加密

    虽然我们给接口加了签名验证,但别人还是可以通过浏览器的开发者工具、或者抓包看到具体传递的参数名和参数值。

    如果不想别人看到这些信息的话,可以将参数都加密,加密方法可以使用RSA加密(使用服务端公钥加密,服务端收到请求后使用私钥解密)、或者AES加密。

    加密后,别人抓包看到的就都是密文了,可以提高别人破解接口的难度。

  • 相关阅读:
    AIDL学习
    给已有vue项目 实现登录功能
    5 个冷门但非常实用的 Kubectl 使用技巧,99% 的人都不知道
    C++多态学习笔记
    Pytorch Advanced(三) Neural Style Transfer
    Django视图(四)
    COMX-P2020、COMX-P1022 开发板
    API 网关的功能
    代理模式——动静态代理,面向接口编程
    java计算机毕业设计高校教师个人信息管理系统MyBatis+系统+LW文档+源码+调试部署
  • 原文地址:https://blog.csdn.net/ljfrocky/article/details/133069903