💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、豆包、星火、月之暗面及文生图、文生视频 广告
## **token的作用**: ### 1.防止表单重复提交 我们在客户端提交表单时,带上一个在隐藏域(<input type="hidden")的token,后台会验证这个token是否被用过.如果不符合,就弹出提示 :Token验证错误! 这样如果用户连续点击提交,也不怕重复提交了. 有些也在前端做了预防措施:比如点击提交按钮后,这个按钮变成灰色不可点击. 具体的例子: 前端生成token: 在application\\admin\\view\\user\\user\\edit.html 中 有  {:token()} ,这个是调用token函数生成一个token. 在thinkphp\\helper.php 的 ``` function token($name = '__token__', $type = 'md5') { $token = Request::instance()->token($name, $type); return '<input type= "hidden" name="' . $name . '" value="' . $token . '" />'; } ``` 直接生成对应的html代码到视图文件; 后台验证token: 在application\common\controller\Backend.php 的 ``` protected function token() { $token = $this->request->param('__token__'); //验证Token if (!Validate::make()->check(['__token__' => $token], ['__token__' => 'require|token'])) { $this->error(__('Token verification error'), '', ['__token__' => $this->request->token()]); } //刷新Token,重新生成一个token,旧的也就失效了 $this->request->token(); } ``` 后台是否进行token验证并不是强制和自动的,如果有需要进行验证,则需要手动调用这个token()函数, 比如:在edit 方法中 ``` if ($this->request->isPost()) { $this->token(); $this->modelValidate = true; //开启验证器 $this->modelSceneValidate =true;//开启场景验证 } ``` ======================================================================= ### 2.用来做身份验证, 流程如下: (1) 客户端使用用户名与密码请求登录 (2) 服务端收到请求, 去验证用户名与密码 (3) 验证成功后 , 服务端会签发一个token, 再把这个token发送给客户端 (4) 客户端收到token后, 可以把它存储起来, 比如放在cookie 中或者Local Storage中 (5) 客户端每次向服务端请求资源时带着服务端签发的token (6) 服务端收到请求, 然后去验证客户端请求里带的token, 如果验证成功, 就向客户端返回请求的数据 在FA里面的流程:(FA的管理后台部分的admin管理员登录状态是使用session的,前台user用户才是使用token) 1. 普通网页登录生成token: application\index\controller\User.php 的 public function login(),验证通过账号密码后,调用 application\common\library\Auth.php 的public function direct($user\_id); 在里面生成token:$this\->\_token = Random::uuid(); 生成的token 是8位-4位-4位-4位-12位, 如:289d3e43-838c-4178-add2-b8e3a276a956 然后把token 写入到 token驱动器里面, 可以选择使用mysql保存或者redis保存,默认是mysql. token数据库驱动器:application\common\library\token\driver\Mysql.php tokenRedis驱动器:application\common\library\token\driver\Redis.php FA默认是使用File驱动器,但实际上并不存在这个文件驱动器. 登录成功后,触发钩子 user\_login\_successed , 钩子实际定义在: application\index\controller\User.php 的public function \_initialize() 中 ``` Hook::add('user_login_successed', function ($user) use ($auth) { $expire = input('post.keeplogin') ? 30 * 86400 : 0; Cookie::set('uid', $user->id, $expire); Cookie::set('token', $auth->getToken(), $expire);         }); ``` 作用就是把token写入到cookie中,并且有效期是30天.随着cookie返回给客户端. 这样在服务端 和客户端都保存了token; 服务端代码要获取当前管理员的用户信息: 2.用API登录生成token: application\api\controller\User.php 的public function login() ;接收账号密码后,很第一步一样的操作,生成token,保存token, 只是在返回给客户端时,不是使用cookies,而是把token 随着接口返回数据一起返回给客户端; 在application\common\library\Auth.php 的public function getUserinfo() 中, ``` $userinfo = array\_merge($userinfo, Token::get($this\->\_token)); return $userinfo; ``` 把Token 并入到用户数据中一起返回,客户端在收到数据后,自己把token 保存起来,一般是写入到Local Storage 里面; 3.使用和验证token: 已修改用户信息为例: 客户端调用application\api\controller\User.php 的public function profile() ,进行用户信息修改 application\common\controller\Api.php 是所有api类的基类,在Api.php的_initialize()初始化函数中, ``` $token = $this->request->server('HTTP_TOKEN', $this->request->request('token', \think\Cookie::get('token'))); //这句话的意思是:先从Header中查有没有叫HTTP_TOKEN的值,不存在的话,再去请求值中查名称是token的值,还不存在的话, 最后去cookies中查token ``` 拿到token 然后去查找:$this\->auth\->init($token); 最终从数据库中查到, 或者从redis中找到这个值, 找不到的话,则会返回错误提示. ``` $this->error(__('Please login first'), null, 401); ``` 上面获取token 用了3种方式,这3种可以对应3种场景: HTTP_TOKEN:对应的一般是app场景,app里面开发者是可以自主设置请求头的内容; request:对应的一般是纯接口场景,比如开放接口给第三方调用,此时不方便要求对方修改或者对方没有权限修请求头的内容,此时,每次调用都需要在请求参数中带上token; cookies:对应的一般是网页场景,使用网页端,不跨域,网站内部调用居多; ### 3.token的加密方式: FA展示给用户看的token是形如:289d3e43-838c-4178-add2-b8e3a276a956; 而在数据库中存储的值是形如:2a86f525289ba66e9e71136c88b5862a1407ad13; 中间使用了加密转换: 使用了application\common\library\token\Driver.php 的 ``` /** * 获取加密后的Token * @param string $token Token标识 * @return string */ protected function getEncryptedToken($token) { $config = \think\Config::get('token'); return hash_hmac($config['hashalgo'], $token, $config['key']); } ``` 在配置文件application\config.php 中 ``` // +---------------------------------------------------------------------- // | Token设置 // +---------------------------------------------------------------------- 'token' => [ // 驱动方式 'type' => 'Mysql', // 缓存前缀 'key' => 'r1CTmdF8ogXL5M9HGyeQPA7I4ZKS2nUE', // 加密方式 'hashalgo' => 'ripemd160', // 缓存有效期 0表示永久缓存 'expire' => 0, ], ```