API安全设计(1)
2021-02-12 13:18
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;
}