好东西要分享

简述接口Token加密认证机制及实现


为何要使用Token认证机制?

简单的说,token适合接口开发进行相关访问授权来避免其他用户进行接口滥用的一种防护措施。无论是前后端分离(例如:小程序开发、Vue、单应用之间的交互等)后需要此方法来进行权限验证。
本篇全文使用ThinkPHP 5.0框架做演示原理的实现, 根本不难主要是也方便自己做一次复盘。

简要的概述实现原理和方法:

客户端拿着账号或秘钥来请求服务端生成Token的接口,服务端返回token给客户端,客户端存储到缓存后续客户端请求服务端其他接口的时候带着这个token,服务端在其他接口前进行token鉴权,成功继续走,失败返回错误信息。

我们还有哪些需要注意的?

token的有效期
token接口每个账号的请求次数
请求数据加密

接下来我们分开来说下,首先就是token的 生成及有效期:

token的有效期可根据自己的项目来定,我们那微信小程序的access_token来做个例子吧,这个access_token有效期是7200 秒,也就是 2 小时,重复刷新会导致上次access_token失效!需要注意的是,为了保证业务的平滑过度,旧的token也需要超时保留 5 分钟可用。OK,我们看下代码的实现:

// 获取 token      public function token()      {          $params = $this->request->param();          // 这个是第三方参数校验,意思是参数不能空且字符串类型          Validation::validate($params, [              'appid' => 'Required|Str',              'secret' => 'Required|Str',          ]);          // 鉴权          $this->authVerification($params['appid'], $params['secret']);          $expires_in = 7200; // 有效期(秒)          $token = md5($params['appid'] . $params['secret'] . time() . '[随机字符串]'); // 生成本次 token          cache($token, time() + $expires_in, $expires_in + 300);    // 缓存 token 参数          $this->queryNumLimit($params['appid']);  // 接口请求次数+1          return json(['token' => $token, 'expires_in' => $expires_in]); // 返回 token 及有效期      }            // 检查用户的账号和秘钥以及接口请求次数上限      private function authVerification($app_id, $secret)      {          $userInfo = db('user')->where('app_id', $app_id)->find();          if (!$userInfo) {              // 抛出异常              throw new BaseException(['msg' => 'appid verification failed']);          }          if ($userInfo['app_secret'] != $secret) {              throw new BaseException(['msg' => 'secret verification failed']);          }          // 账号禁用          if ($userInfo['status'] != 10) {              throw new BaseException(['msg' => 'This account has been disabled']);          }          // 请求次数达到上限          if ($this->getQueryNum($app_id) > $userInfo['query_num']) {              throw new BaseException(['msg' => 'The number of requests reached the limit']);          }          return true;      }

为了方便演示,这里创建一个数据表进行模拟:

SET NAMES utf8;  SET time_zone = '+00:00';  SET foreign_key_checks = 0;  SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';    DROP TABLE IF EXISTS `qd_user`;  CREATE TABLE `qd_user` (    `user_id` int(11) NOT NULL AUTO_INCREMENT,    `app_id` varchar(30) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '账号应用 ID',    `app_secret` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '账号应用 key',    `salt` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '盐',    `status` tinyint(3) NOT NULL DEFAULT '10' COMMENT '账号状态 10 正常 30 封禁',    `query_num` int(11) NOT NULL DEFAULT '3000' COMMENT '请求次数限制',    `create_time` int(11) NOT NULL DEFAULT '0',    `update_time` int(11) NOT NULL DEFAULT '0',    PRIMARY KEY (`user_id`),    UNIQUE KEY `app_id` (`app_id`)  ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';

此表设计涉及到盐加密,如果觉得麻烦的就删除此字段吧。
我们大概说下token方法的实现思路:
首先我们先检查appid和secret是否正确,那正确的就看下属于这个appid的请求次数是否达到上限,这个后面也可以按具体接口应用的appid进行限制处理。
然后,我们定义一个token的有效期,这里是7200秒,我们将生成的token进行缓存,缓存的键就是这个token值就是当前时间+有效期,失效时间就是有效期+300 也就是保留5分钟的过度时间。
最后我们增加下这个appid的请求次数,然后返回token给客户端即可。

// 客户端请求接口获取 token      public function getToken()      {          // 因为有请求次数限制,所以我们需要把每次请求回来的 token 进行缓存,缓存时间比服务端接口的有效期略小就 ok          if (!cache('queryToken')) {              // 这里用到了curl 内置封装方法,随便百度即可              $token = curl(config('bppUrl') . 'api/getToken?appid=7e2d8335&secret=9c4a72f0cc64c');              cache('queryToken', $token,7000);          }          return json_decode(cache('queryToken'), true);      }

token的验证:

我们的token验证还是比较简单的,上一步我们将当前时间+有效期时间存储到了已token为键的缓存中,这次客户端拿着token来请求服务端接口,服务端去用这个token读缓存的值,把读出来的值与当前时间对比,如果这个值小于了当前时间,那么表示token过了有效期,当然token不存在什么的就很好判断了。
另外,多说一句,如果所有接口都需要验证此token,那么token验证直接写在基类即可

   // ThinkPHP 自带控制器初始化方法      public function _initialize()      {          $this->checkToken(input('token');           }            // token 校验      private function checkToken($token)      {          // token 不存在          if (empty($token) || !cache($token)) {              throw new BaseException(['msg' => 'Token verification failed']);          }          // token 失效          if (cache($token) < time()) {              throw new BaseException(['msg' => 'Token has expired']);          }          return true;      }

接下来我们说说请求次数:

我们为什么要给这个接口增加请求次数限制?还是为了避免自己的接口凭证泄露的一种保护措施,也是减少服务端压力的一种有效措施。这里我们还是使用缓存来做:

    // 增加请求次数      private function queryNumLimit($appid)      {          if (!cache($appid . 'tokenNum')) {              // 缓存到当前的 24 点,24 点之后重新计算次数              cache($appid . 'tokenNum', 1, strtotime(date('Y-m-d', time())) + 24 * 60 * 60 - time());          }          Cache::inc($appid . 'tokenNum');      }            // 获取当前调用次数      private function getQueryNum($appid)      {          return cache($appid . 'tokenNum');      }

到了这里,我们要说的 token 完整的实现基本就差不多了,下面说点题外的

关于上次开发的单应用之间的数据同步对接。token的有效期总是有一段时间的,如果发生接口泄露,那么一个脚本在1秒直接做很多事情了,更何况几千秒呢?
所以为了保证数据的安全,数据加密有时候也是必不可少的,我们经常对接一些支付接口,腾讯的接口都会有个sdk包,里面就含有秘钥的获取和参数的加密,这足以证明上面我们说的token机制实际上只能做一些内部应用接口上,一旦应用到公开接口比如说 商城的订单处理、工具类的视频上传等等,还是需要单独开发一套完整的认证系统的。

总结

总的来说,本篇主要讲解了token生成和认证的实现思路,主要的意义在于给自己开发的单应用之间的数据交互做一次复盘,还有目前在做一个开放接口,但是接口数量上还是比较少的还没有开源出来,有兴趣的话可以联系我一起加入维护进来,也是为了给自己将来开发的道路上带来一点方便吧。

相关推荐

  • 暂无文章

评论 抢沙发

评论前必须登录!