美文网首页
如何设计签名保护接口安全

如何设计签名保护接口安全

作者: _不能说的秘密i | 来源:发表于2019-03-27 19:47 被阅读0次

    参考: http://www.cnblogs.com/codelir/p/5327462.html

    签名的主要作用

    • 请求来源(身份)是否合法?
    • 请求的唯一性(不可重复请求)

    步骤

    • 服务端给客户端分配 app_id app_secret
    • 客户端和服务端通过指定的算法生成签名 sign
    • 客户端每次请求都把 app_id 和生成好的 sign 放到请求参数中传递给服务端
    • 服务端拿出客户端传递的 app_id 对应的 app_secret 通过算法生成一个 sign 和客户端传递的 sign 对比

    注意: app_secret 只是为了生成 sign 用的,请不要将 app_secret 放到请求参数中

    关于算法

    • 将所有请求参数加入到算法中
    • 将时间戳加入到算法中(注意: js获取的时间戳和PHP获取的时间戳格式不一致)
    • app_secret 加入到算法中

    举个例子, 此处以 jquery 和 laravel 为例

    使用 axios 的话,原理也是一样的

    • 客户端代码
    <script src="jquery.min.js"></script>
    <script>
        $(function () {
            // 请求参数
            var params = {
                id: 1,
                app_id: 'xKYf550dzriIVeZbrcz2WpAMV0SOMCelpUcNfLDWm9vf1BdsT41uvqVE3PLH7lQx'
            };
    
            /**
             * 生成签名加密算法
             * @param $params
             */
            function signGenerator($params) {
                // 获取秘钥和请求参数循环组成: secret:key=value&key=value&timestamp
                var secret = 'fw2FZjjmvKlpoByizWfScZbAluXdNwooABu93LEdKRV5ZOtJKrL7LEosb4IGBw4W';
    
                var sign = secret + ':';
                for (var key in params) {
                    sign += key + '=' + $params[key] + '&';
                }
    
                // 获取PHP格式的时间戳
                var timestamp = (new Date()).getTime() / 1000;
                console.log("客户端生成的签名:", window.btoa(sign + Math.floor(timestamp)));
    
                // window.btoa是js内置的base64加密算法
                return window.btoa(sign + Math.floor(timestamp));
            }
    
            // 发送请求
            $.ajax({
                type: "GET",
                url: "https://www.example.com/api/test",
                data: params,
                beforeSend: function (request) {
                    // 将签名添加到请求头中
                    request.setRequestHeader("sign", signGenerator(params));
                },
                success: function (response) {
                    console.log(JSON.stringify(response));
                }
            });
        });
    </script>
    
    • 服务端代码
      此处使用的是laravel的中间件
    <?php
    
    namespace App\Http\Middleware;
    
    use Closure;
    
    /**
     * 签名验证中间件
     * Class SignVerify
     * @package App\Http\Middleware
     */
    class SignVerify
    {
        /**
         * Handle an incoming request.
         *
         * @param  \Illuminate\Http\Request $request
         * @param  \Closure $next
         * @return mixed
         */
        public function handle($request, Closure $next)
        {
            // 此处的 signList 是所有允许的 app_id 和 app_secret
            // 正常情况下应该是存放到数据库
            // 我写死放到这里, 只是为了演示效果
            $signList = [
                [
                    'app_id'     => 'xKYf550dzriIVeZbrcz2WpAMV0SOMCelpUcNfLDWm9vf1BdsT41uvqVE3PLH7lQx',
                    'app_secret' => 'fw2FZjjmvKlpoByizWfScZbAluXdNwooABu93LEdKRV5ZOtJKrL7LEosb4IGBw4W'
                ],
                //...
                [
                    'app_id'     => 'aaaaaaaaaa',
                    'app_secret' => 'bbbbbbbbbb'
                ]
            ];
    
            // 获取客户端传递的 app_id 匹配服务端的 app_id对应的 值
            $client_app_id = $request->input('app_id');
            $server_app_secret = null;
            foreach ($signList as $key => $value) {
                if ($value['app_id'] == $client_app_id) {
                    $server_app_secret = $value;
                    break;
                }
                continue;
            }
    
            if (is_null($server_app_secret)) {
                return 'Signature verification failed';
            }
    
            // 获取 header 中客户端传递的 sign 然后, 和服务端生成的sign对比
            $client_sign = $request->header('sign');
    
            // 服务端生成签名(循环生成, 如果直接使用当前时间戳可能有网络延迟问题)
            $time   = time();
            $params = $request->all();
            $expire = 3; // 3内秒钟有效, 可以将这个放到配置文件中
            for ($i = 0; $i < $expire; $i++) {
                // 签名正确就往后执行
                if ($this->signGenerator($params, $server_app_secret, $time + $i) == $client_sign) {
                    return $next($request);
                }
                continue;
            }
            return 'Signature verification failed';
        }
    
        /**
         * 生成签名
         *
         * @param $params   请求参数
         * @param $secret   秘钥
         * @param $time     时间戳
         * @return string
         */
        public function signGenerator($params, $secret, $timestamps)
        {
            $secret .= ':';
            foreach ($params as $key => $value) {
                $secret .= $key . '=' . '&';
            }
    
            // base64
            return base64_encode($secret . $timestamps);
        }
    
    }
    

    相关文章

      网友评论

          本文标题:如何设计签名保护接口安全

          本文链接:https://www.haomeiwen.com/subject/hietbqtx.html