API安全设计(1)

1、API简介

这段时间和外部公司合作,一直在写对外API接口。提供的API接口是基于http协议的,也是无状态的。每次请求都必须带上身份认证信息。后台服务对身份信息进行校验。
基于HTTP协议的API身份认证有很多种方式,有HTTP Basic,HTTP Digest,API KEY,Oauth,JWK等方式,我这里只讲基于项目中的API KEY校验。

2、API KEY

API接口均通过请求头(HEADER)中传递的 TOKEN(授权令牌)来进行身份认证和鉴权, 系统会在校验 TOKEN 的正确性和时效性。
API Key就是经过用户身份认证之后服务端给客户端分配一个API Key,类似:http://test/api/package/create,
将生成的token放在头部进行传输。

一般的处理流程如下: 一个简单的设计示例如下:

3、客户端生成TOKEN

计算sign和token的PHP参考代码:
设计思路:
user_id: 授权客户的 id;
app_key:开通 API 授权后获得的私钥;
ts: 发起请求时的时间戳, 精确到秒,这个值跟接收到请求时的服务器时间戳,值偏差(正或负)超过 1200 秒( 20 分钟)时,请求会被拒绝,要求调用方重新生成;
token(请使用 CCT +08:00 中国北京时间);
sign: 签名字符串,对输入参数的键值key进行升序排列,转换成型如k1=v1&k2=v2的字符串,然后拼接时间戳和app_key,最后进行sha1混淆;

function getToken($inputArr)
{
    //当前unix时间戳
    $userId = '3322991';
    $appKey = 'abc123';
    $ts = time();
    $sign = get_sign($inputArr, $ts, $appKey);
    $token = base64_encode($userId . ',' . $ts . ',' . $sign);

    return $token;
}

function getSign($inputArr, $ts, $appKey)
{
    ksort($inputArr);
    $inputStr = urlencode(http_build_query($inputArr));
    $sign = sha1($inputStr . $ts . $appKey);
    return $sign;
}


4、服务端解析TOKEN

后台服务端解析TOKEN的主要思路是:
1、从头部header获取token参数;
2、根据token得到user_id、ts和sign;
3、然后根据user_id、ts和请求参数新生成签名sign;
4、校验app_key是否合法,校验ts的时效性,校验新生成的签名sign和传的签名sign是否一致。

    /**
     * 解析TOKEN
     * @param $token
     * @param array $inputArr
     * @return array
     */
    function decToken($token, $inputArr = [])
    {
        $tokenInfo = base64_decode($token);
        $tokenInfo = explode(',', $tokenInfo);
        if (count($tokenInfo) != 3) {
            Error::trigger('TOKEN信息错误');
        }
        
        list($userId, $time, $sign) = $tokenInfo;
        Log::info("check token params,userId:{$userId},time :{$time},sign:{$sign},inputArr:" . json_encode($inputArr));
        
        // 时间有效性校验
        if (abs(time() - $time) > App::getConfig('api_token_expire_time')) {
            Error::trigger(Error::ERR_PARAM_TOKEN_TIME);
        }
        
        // 签名校验
        $tokenObj = new Token();
		// 获取用户appKey信息,根据实际项目生成app_key的规则而定
        $appKey = '*****************';
        
        $generateSign = $this->getSign($inputArr, $time, $appKey);
        
        // 校验参数生成的token
        $newToken = $this->getToken($inputArr, $userId, $appKey, $time);
        Log::info("token:{$token},newToken:" . json_encode($newToken));
        Log::info("sign:{$sign},newSign:" . json_encode($generateSign));
        
        if ($sign !== $generateSign) {
            Error::trigger(Error::ERR_PARAM_TOKEN_SIGN);
        }
        
        Log::info('token decoded successfully');
        return $appKey;
    }
原文地址:https://www.cnblogs.com/jaspersong/p/8474617.html