前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【附近的人】系列之API安全(一)

【附近的人】系列之API安全(一)

作者头像
老李秀
发布2019-11-14 10:24:39
6740
发布2019-11-14 10:24:39
举报

我是老李,大家好,众所周知【附近的人】系列有一段时间没有更新了,搞得好像太监了似的,然而并没有...这一系列只是周期不太稳定、不太调而已,原因说来倒也简单:

  • 一来是毕竟我有我自己的安排(主要是懒)
  • 二来毕竟是免费的(不收钱,说话就是硬气!)

当然了,上述两条都是扯,主要是我想告诉你们的是:自己的事情一旦想好了,节奏自己一定要自己把握,你可以听取别人的意见,但绝不能被其他影响到自己节奏把控甚至最后改变想法。其实,我还是很勤奋的,不信看github,不能说每天一点绿吧,至少一周还是有两三点绿的...

涡轮增压短期猛搞乃一时躁,大排自吸长期持久为永恒王道

其实很久(大概六个月)之前,本篇标题里的内容我就简单讨论过一些关于API安全的内容。只不过,毕时过境迁、如白驹过隙、任时光荏苒、怎物是人非,任由岁月在我稚嫩的脸庞上留下时光的痕迹,自那琐碎如捧在手里的水从间隙中流走,就在这个时候一旁边打游戏边撇我写文章的永强说[ 你TM这两天看<情感读书会>看多了吧,给人投稿呢? ]听到他的这句话后的我立马停止了这种文风回归到以前,(承接上文)如今正好再来一波儿放到【附近的人】服务系列文章中由其自成一章,并与尼古拉斯永强的安全系列文章环环相扣,想必定能承上启下、承前启后、画龙点睛[ 就在这个时候永强又撇了过来说了一句:你TM这是在你们公司和他们玩成语接龙玩石乐志么? ]于是我就停了不再继续写成语了...

首先,在山人看来,API安全主要是指两个方面:

  • 一来是API本身尽可能低概率的被滥用:这里主要是指被人给扒裤衩了恶意调用了,curl随便搞,脚本嘻唰唰,甚至第三方客户端
  • 二来是API和端飞数据尽可能低概率被其他人窥探到:这里主要是说飞过来的参数抑或飞回去的处理结果,能加密最好加密
  • 三是防刷限流,不过我打算把限流单独拿出去说下

请注意,前两条里我都用到了【尽可能低概率】这几个字,而不是类似于【三招教你杜绝xxx】的口吻。

在正式开始之前,我觉得我们还是有必要聊一聊token。API年代,不再像以前MVC套模板那种万金油套路了,讲究的是前后端分离,不分不高端,不分不舒服斯基。

只不过PC年代用户登录后,用户的登录凭证一般都是利用session/cookie(这里把session和cookie放到一起了,因为绝大多数情况下session需要依赖cookie得以实现)这种方式,因为客户端大多都是浏览器,而session/cookie可以说是为浏览器而生;而到了移动互联网年代,一般都是前后分离的纯API,在C/S里还要继续使用cookie承载用户登录凭证不太合适,这会儿大家都会用token来代替session/cookie...当然了,非要在APP端里用cookie/session。。。。。。(没太搞明白session机制的同学有兴趣可以看下本次推送的第二篇文章或者链接https://t.ti-node.com/thread/6445811931751120897)


先聊聊TOKEN

我觉得在此有必要先聊一聊token的常规使用流程,其实token本质上就是我们又重新发明了一个自定义版的session,这句话你们好好琢磨琢磨。当前市面上,由于JWT的诞生出现,使得几乎在所有讨论有关token的地方都会自动分成两大派:

  • JWT,token压根不用单独存储处理的,直接用,不香吗?
  • token和用户信息(主要是uid)自己生成token,以kv方式存储到kv介质中去,对token掌控更多,总之就是自己发明的野路子

这两派人士往往会在出现token这种技术方案的任何场所中进行友好的磋商和探讨,使得智慧的火花在碰撞中不断产生:

正如上图所示:讨论将会在一篇祥和的氛围中结束,双方一起总结过去token方案的成就与不足,展望未来一年token方案的方向和目标!

所以,我就干脆两派的方案都尬聊一下得了...

先说下JWT的简单构成:header + body(专业说法叫payload) + (header+body的消息认证码),按理说就是这么简单,实际上也可能真的就是这么简单。只不过关于消息认证码,这个我非常非常强烈建议你们抽空看下《我赵永强又回来了:单散、认证与数签(五上)》,永强在这篇里用非常粗暴方式快速介绍了消息认证码,这里我就不在当复读机了。

根据jwt的构成原理,我这儿用PHP码了一坨简单的demo代码,你们先复制粘体走感受下:

<?php
$hash_alg = 'sha256';
$password = 'wahaha';
function jwt_encode( $data ) {
  return rtrim( strtr( base64_encode( $data ), '+/', '-_' ), '=' );
}
function jwt_decode( $data ) {
  return base64_decode( str_pad( strtr( $data, '-_', '+/' ), strlen($data) % 4, '=', STR_PAD_RIGHT ) );
}
/*
    ------------ JWT header ----------------
 */
$jwt_header_arr = array(
  'alg' => $hash_alg,
  'typ' => 'JWT',
);
/*
    ------------ JWT body ----------------
    其中,前七个都是jwt官网规定必须规范字段
    最后一个uid是我自己搞的撒...
 */
$jwt_body_arr = array(
  'iss' => 'admin',
  'exp' => time() + 600,
  'sub' => 'test',
  'aud' => 'every',
  'nbf' => time(),
  'iat' => time(),
  'jti' => 10001,
  'uid' => 32142353484,
);
$jwt_header = jwt_encode( json_encode( $jwt_header_arr ) );
$jwt_body   = jwt_encode( json_encode( $jwt_body_arr ) );
// 利用hash_hmac计算出jwt_header与jwt_body的消息认证码
$jwt_sign   = hash_hmac( $hash_alg, $jwt_header.'.'.$jwt_body, $password );
$jwt_token  = $jwt_header.'.'.$jwt_body.'.'.$jwt_sign;
echo "下面是生成的jwt_token".PHP_EOL;
echo $jwt_token.PHP_EOL;
echo "下面解析并校验jwt_token".PHP_EOL;
$jwt_token_arr = explode( '.', $jwt_token );
$jwt_body_json = jwt_decode( $jwt_token_arr[ 1 ] );
echo $jwt_body_json.PHP_EOL;

运行一下出了个结果,你们感受下,感觉和jwt挺相似的,应该是成功了:

所以用的时候咋用?就按照上述代码相反的算法来一波儿就行,确切说就是先base64_decode一下,然后在json_decode,但是这就可以了?当然不是,因为还需要利用最后的消息认证码来对数据完整性进行确认(注意上述demo代码里并没有实现对数据完整性进行校验的代码,你们自己补一下吧...),其实你可以粗暴理解为【验签】~

用法就是:

  • 客户端通过用户名密码登录,获取到服务端生成的jwt
  • 客户端把jwt存起来,访问API的时候带过去,一般说来不成文的规定就是放到http header中去,当然你扔到http body体甚至query string里都没人管你
  • 服务端收到请求后,取出jwt,然后decode并校验jwt数据完整性,然后从jwt_body里可以拿到uid或者是你们规定的其他信息,顺利识别出用户是谁

听起来好像还不赖,反正能用...那么,在此之前或者说与jwt并不完全一样的token思路,比如有这么实现的:就是利用AES加密(对AES加密需要简单了解下的同学请在公众号中抠“加密”两个字)。我写个简单的demo你们复制粘贴走感受下,主要是思路(请务必仔细看注释),你不一定也非要用imei和uid,你可以用任何【非敏感】数据来实现这个大致思路:

<?php
/*
  (imei和token都假设在http header中)
  思路就是拿设备imei当作aes加密密钥,给uid+imei进行加密,结果当token
  用户登录成功后,把这个token返回给客户端
  客户端保存好token,访问接下来api带上这个token以及imei
  API收到token后,利用imei进行解密,如果【能解密成功并且解密后的imei等于
  API访问提交上来imei】注意这里要当两个条件同时并列成立,就算是有效token
*/
// 采集到的设备id
$imei = 'MD101C4MX30I95ZX6A';
// 用户uid 
$uid = 4123456767;
// aes加密采用具体分组算法
$aes_method = 'aes-256-cbc';
// 密钥
$key  = $imei;
$data = $imei.$uid;
// aes-128-cbc分组加密算法需要的iv向量的长度
$iv_length = openssl_cipher_iv_length( $aes_method );
// 根据长度生成相应iv
$iv = openssl_random_pseudo_bytes( $iv_length, $cstrong );
echo "明文:".$imei.$uid.PHP_EOL;
$enc_data = base64_encode( openssl_encrypt( $data, $aes_method, $key, OPENSSL_RAW_DATA, $iv ) );
echo "秘文:".$enc_data.PHP_EOL;
$dec_data = openssl_decrypt( base64_decode( $enc_data ), $aes_method, $key, OPENSSL_RAW_DATA, $iv );
echo "解密:".$dec_data.PHP_EOL;

// 注意!⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
// 我偷懒了,依然没有写校验部分,很简单你们自己补充下哈...

保存一下,运行结果就是这样shai儿:

这个思路和jwt不一样的就是:jwt的payload部分实际本质上是明文,一次base64-decode你就可以看到明文是什么,所以你一定要通过hash_hmac系列函数来对中间的payload进行数据完整性校验,从而保证payload被篡改后立马就会无效;而加密的方案相当于对payload进行aes加密,如果payload被篡改,解密会失败且校验无法通过,如果说token泄露被copy走了,那么由于imei发生变化,也会导致token校验失败从而杜绝信息泄露,除非这家伙猜到了我们用imei参与了token运算。

除此之外,还有一些方案就是将token和用户信息以[ key : value ]的方式存入到redis或memcache中去,API收到token后会从redis或memcache中获取到用户信息...

在token的生成方式以及使用方式上叨叨这么多,其实就是想说明一个问题:没有对和错,方法多种多样,你可以博采众长,最终能用就行...(如果你们有什么好的想法或者指出我的错误,请后台留言我会在下篇贴出来的)

未完待续...

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 高性能API社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档