接口安全
基于JWTtoken认证
thinkphp的jwt(JSON Web Token)身份验证包。支持Header、Cookie、Param等多种传参方式。包含:验证、验证并且自动刷新等多种中间件。
~~~
此举将生成jwt.php和.env配置文件。不推荐直接修改jwt.php 同时,env中会随机生成secret。请不要随意更新secret,也请保障secret安全。
使用
对于需要验证的路由或者模块添加中间件:
示例:
token刷新说明:
token默认有效期为60秒,如果需要修改请修改env文件。 refresh\_ttl为刷新token有效期参数,单位为分钟。默认有效期14天。 token过期后,旧token将会被加入黑名单。 如果需要自动刷新,请使用中间件 thans\\jwt\\middleware\\JWTAuthAndRefresh::class, 自动刷新后会通过header返回,请保存好。(注意,此中间件过期后第一次访问正常,第二次进入黑名单。)
token传参方式如下:
可通过jwt.php配置文件内token\_mode参数来调整参数接收方式及优先级 token\_mode默认值为\[‘header’, ‘cookie’, ‘param’\];
在某些前后端分离的情况下可选择取消cookie接收方式来避免token冲突
将token加入到url中作为参数。键名为token
将token加入到cookie。键名为token
将token加入header,如下:Authorization:bearer token值
以上三种方式,任选其一即可。推荐加入header中。
其他操作
拉黑Token JWTAuth::invalidate($token);
查询Token是否黑名单 JWTAuth::validate($token);
常见问题
修改public/.htaccess文件,通过apache重写,处理HTTP请求中的Authorization字段
使用RSA256方式的时候,请使用文本形式。
参考文档:[https://github.com/tymondesigns/jwt-auth](https://github.com/tymondesigns/jwt-auth)
接口的安全性主要围绕Token、Timestamp和Sign三个机制展开设计,保证接口的数据不会被篡改和重复调用,下面具体来看:
Token授权机制:用户使用用户名密码登录后服务器给客户端返回一个Token(通常是UUID),并将Token-UserId以键值对的形式存放在缓存服务器中。服务端接收到请求后进行Token验证,如果Token不存在,说明请求无效。Token是客户端访问服务端的凭证。
时间戳超时机制:用户每次请求都带上当前时间的时间戳timestamp,服务端接收到timestamp后跟当前时间进行比对,如果时间差大于一定时间(比如5分钟),则认为该请求失效。时间戳超时机制是防御DOS攻击的有效手段。
签名机制:将 Token 和 时间戳 加上其他请求参数再用MD5或SHA-1算法(可根据情况加点盐)加密,加密后的数据就是本次请求的签名sign,服务端接收到请求后以同样的算法得到签名,并跟当前的签名进行比对,如果不一样,说明参数被更改过,直接返回错误标识。签名机制保证了数据不会被篡改。
拒绝重复调用(非必须):客户端第一次访问时,将签名sign存放到缓存服务器中,超时时间设定为跟时间戳的超时时间一致,二者时间一致可以保证无论在timestamp限定时间内还是外 URL都只能访问一次。如果有人使用同一个URL再次访问,如果发现缓存服务器中已经存在了本次签名,则拒绝服务。如果在缓存中的签名失效的情况下,有人使用同一个URL再次访问,则会被时间戳超时机制拦截。这就是为什么要求时间戳的超时时间要设定为跟时间戳的超时时间一致。拒绝重复调用机制确保URL被别人截获了也无法使用(如抓取数据)。
整个流程如下:
1、客户端通过用户名密码登录服务器并获取Token
2、客户端生成时间戳timestamp,并将timestamp作为其中一个参数
3、客户端将所有的参数,包括Token和timestamp按照自己的算法进行排序加密得到签名sign
4、将token、timestamp和sign作为请求时必须携带的参数加在每个请求的URL后边([http://url/request?token=123×tamp=123&sign=123123123)](http://url/request?token=123%C3%97tamp=123&sign=123123123%EF%BC%89)
5、服务端写一个过滤器对token、timestamp和sign进行验证,只有在token有效、timestamp未超时、缓存服务器中不存在sign三种情况同时满足,本次请求才有效
在以上三中机制的保护下,
如果有人劫持了请求,并对请求中的参数进行了修改,签名就无法通过;
如果有人使用已经劫持的URL进行DOS攻击,服务器则会因为缓存服务器中已经存在签名或时间戳超时而拒绝服务,所以DOS攻击也是不可能的;
如果签名算法和用户名密码都暴露了,那齐天大圣来了估计也不好使吧。。。。
最后说一句,所有的安全措施都用上的话有时候难免太过复杂,在实际项目中需要根据自身情况作出裁剪,比如可以只使用签名机制就可以保证信息不会被篡改,或者定向提供服务的时候只用Token机制就可以了。如何裁剪,全看项目实际情况和对接口安全性的要求
实例代码
后端验证
class Safe
{
/**
* 处理请求
*
* @param \think\Request $request
* @param \Closure $next
* @return Response
*/
public function handle($request, \Closure $next)
{
try {
//
# 验证token 、可以直接加在路由后面
# 验证时间戳
$this->checkTime();
# 验证签名
$this->checkSign();
return $next($request);
}catch (Exception $exception) {
return json($exception->getMessage());
}
}
/**
* 验证时间戳
* @throws Exception
*/
public function checkTime(){
$client_time = request()->get('timestamp') ?: request()->post('timestamp');
if (!is_numeric($client_time)) {
throw new Exception('时间戳格式不正确');
}
if (time() - $client_time > 120) {
throw new Exception('请求超时');
}
}
/**
* 检查签名
*/
public function checkSign(){
$client_sign = request()->get('sign') ?: request()->post('sign');
# 判断是否有签名
if (!$client_sign) {
throw new Exception('签名不正确');
}
# 判断签名是否正确
$server_sign = $this->getSign();
if ($client_sign != $server_sign) {
throw new Exception('签名不正确');
}
}
/**
* 获取服务端签名
* @return string
*/
public function getSign(){
# 获取所有请求的参数
$params = request()->all();
# 签名规则
# 第一步 参与签名的参数不包括签名本身、不包括token
unset($params['sign']);
unset($params['token']);
# 第二步 按照ASCII排序
ksort($params);
$wait_sign = '';
foreach ($params as $key=> $value) {
$wait_sign .= $key.'='.$value.'&';
}
# 去除多余的& 符号
$wait_sign = rtrim($wait_sign,'&');
return md5($wait_sign);
}
}
前端生成sign
<script src="../javaScript-MD5/js/md5.js"></script>
<script>
// # 获取所有请求的参数
// # 签名规则
// # 第一步 参与签名的参数不包括签名本身、不包括token
// # 第二步 按照ASCII排序
// # 去除多余的& 符号
var params = new Array();
params['id'] = 1;
params['name'] = '张三';
var sign = createSign(params);
params['sign'] = sign;
var url = 'http://pyg.com/list?'
for (var i in params) {
url += i + '=' + params[i] + '&';
}
$.ajax({
url:url,
dataType:'json',
success:function (result) {
console.log(result);
}
})
function createSign(params) {
var timestamp = Math.ceil((new Date()).getTime()/1000);
params['timestamp'] = timestamp;
params.sort();
var wait_sign = ''
for (var i in params) {
wait_sign += i + '=' + params[i] + '&';
}
wait_sign = wait_sign.substr(0,wait_sign.length-1);
console.log(wait_sign)
sign = hex_md5(wait_sign)
return sign;
}
</script>
接口限流
接口限流:限制接口的访问频率
限流,顾名思义,就是限制对 API 的调用频率。每一次 API 调用,都要花费服务器的资源,因此很多 API 不会对用户无限次地开放,请求达到某个次数后就不再允许访问了,或者一段时间内,最多只允许访问 API 指定次数。
目前,我们的接口是没有任何限流措施的,只要用户调用接口,服务器就会处理并返回数据。为了防止接口被恶意用户刷爆,我们来给接口限流
实战
请尝试:
ThinkPHP6 接口频繁访问限制
作用
通过本中间件可限定用户在一段时间内的访问次数,可用于保护接口防爬防爆破的目的。
接口频率限制
注:限制接口访问频率限制,不是用户,也不是ip
命令,安装插件