Fastadmin开发两个APP端,接口token验证
假设两个APP分别是用户端和内部端
结构如下
API (用户端接口)
/api/ ├── auth/ # 认证相关 ├── users/ # 用户相关 ├── products/ # 商品相关 └── orders/ # 订单相关
Internal (内部端接口)
/internal/ ├── auth/ # 认证相关 ├── users/ # 用户相关 ├── products/ # 商品相关 └── orders/ # 订单相关
找到文件 application/common/library/Auth.php
增加一个类型
// 新增类型标识属性,api客户端,internal内部端 protected $type = 'api';
在instance方法中增加处理参数的代码
// 处理类型参数 if (is_array($options) && isset($options['type'])) { self::$instance->setType($options['type']); }
public function setType($type) { $this->type = $type; // 加载对应类型的配置 $config = Config::get("auth_groups.{$type}", []); // 合并配置到options $this->options = array_merge($this->options, $config); return $this; }
Token初始化中处理前缀
/** * 根据Token初始化 * * @param string $token Token * @return boolean */ public function init($token) { if ($this->_logined) { return true; } if ($this->_error) { return false; } // 验证Token前缀 if (!preg_match("/^{$this->type}_/", $token)) { $this->setError('Invalid token type'); return false; } $data = Token::get($token); if (!$data) { return false; } $user_id = intval($data['user_id']); $modelClass = $this->options['model'] ?? User::class; // 动态模型 if ($user_id > 0) { // $user = User::get($user_id); $user = $modelClass::get($user_id); if (!$user) { $this->setError('Account not exist'); return false; }
修改register方法中的设置token
//设置Token // $this->_token = Random::uuid(); $rawToken = Random::uuid(); $this->_token = "{$this->type}_{$rawToken}";
login方法中动态获取登录字段
public function login($account, $password) { // $field = Validate::is($account, 'email') ? 'email' : (Validate::regex($account, '/^1\d{10}$/') ? 'mobile' : 'username'); // $user = User::get([$field => $account]); // 动态获取登录字段 ------------------------- $field = $this->detectLoginField($account); $modelClass = $this->options['model'] ?? User::class; $user = $modelClass::get([$field => $account]); // ----------------------------------------- if (!$user) { $this->setError('Account is incorrect'); return false; }
// 新增登录字段检测方法 --------------------------- protected function detectLoginField($account) { // 从配置获取字段检测规则 $loginField = $this->options['login_field'] ?? ['username', 'email', 'mobile']; if (is_string($loginField)) { return $loginField; // 直接指定字段 } // 默认检测逻辑 foreach ($loginField as $field) { if ($field === 'email' && Validate::is($account, 'email')) { return 'email'; } if ($field === 'mobile' && Validate::regex($account, '/^1\d{10}$/')) { return 'mobile'; } } return 'username'; }
直接登录账号direct方法
public function direct($user_id) { // $user = User::get($user_id); $modelClass = $this->options['model'] ?? User::class; $user = $modelClass::get($user_id);
// .....原代码
// $this->_token = Random::uuid();
$rawToken = Random::uuid();
$this->_token = "{$this->type}_{$rawToken}";
Token::set($this->_token, $user->id, $this->keeptime);
获取会员基本信息中增加账号类型
/** * 获取会员基本信息 */ public function getUserinfo() { $data = $this->_user->toArray(); $allowFields = $this->getAllowFields(); $userinfo = array_intersect_key($data, array_flip($allowFields)); $userinfo = array_merge($userinfo, Token::get($this->_token)); $userinfo['avatar'] = url($userinfo['avatar'], '', false, true); // 账号类型 $userinfo['account_type'] = $this->type; return $userinfo; }
给内部端设置用户表
CREATE TABLE `fa_internal_user` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', `group_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '组别ID', `username` varchar(32) DEFAULT '' COMMENT '用户名', `nickname` varchar(50) DEFAULT '' COMMENT '昵称', `password` varchar(32) DEFAULT '' COMMENT '密码', `salt` varchar(30) DEFAULT '' COMMENT '密码盐', `email` varchar(100) DEFAULT '' COMMENT '电子邮箱', `mobile` varchar(11) DEFAULT '' COMMENT '手机号', `avatar` varchar(255) DEFAULT '' COMMENT '头像', `level` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '等级', `gender` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '性别', `birthday` date DEFAULT NULL COMMENT '生日', `bio` varchar(100) DEFAULT '' COMMENT '格言', `money` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '余额', `score` int(10) NOT NULL DEFAULT '0' COMMENT '积分', `successions` int(10) unsigned NOT NULL DEFAULT '1' COMMENT '连续登录天数', `maxsuccessions` int(10) unsigned NOT NULL DEFAULT '1' COMMENT '最大连续登录天数', `prevtime` bigint(16) DEFAULT NULL COMMENT '上次登录时间', `logintime` bigint(16) DEFAULT NULL COMMENT '登录时间', `loginip` varchar(50) DEFAULT '' COMMENT '登录IP', `loginfailure` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '失败次数', `loginfailuretime` bigint(16) DEFAULT NULL COMMENT '最后登录失败时间', `joinip` varchar(50) DEFAULT '' COMMENT '加入IP', `jointime` bigint(16) DEFAULT NULL COMMENT '加入时间', `createtime` bigint(16) DEFAULT NULL COMMENT '创建时间', `updatetime` bigint(16) DEFAULT NULL COMMENT '更新时间', `token` varchar(50) DEFAULT '' COMMENT 'Token', `status` varchar(30) DEFAULT '' COMMENT '状态', `verification` varchar(255) DEFAULT '' COMMENT '验证', PRIMARY KEY (`id`), KEY `username` (`username`), KEY `email` (`email`), KEY `mobile` (`mobile`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='内部端会员表';
增加Model 文件 application/common/model/InternalUser.php
增加内部端API基类 application/common/controller/InternalApi.php ,在internal模块下面controller都继承这个基类
<?php namespace app\common\controller; use app\common\library\Auth; use think\Config; use think\Hook; use think\Loader; use think\Validate; /** * 内部端API基类 */ class InternalApi extends Api { /** * 初始化操作 (重写父类方法) */ protected function _initialize() { // 跨域请求检测 check_cors_request(); // 检测IP是否允许 check_ip_allowed(); // 移除HTML标签 $this->request->filter('trim,strip_tags,htmlspecialchars'); // 初始化批发商端权限实例 $this->auth = Auth::instance(['type' => 'internal']); $modulename = $this->request->module(); $controllername = Loader::parseName($this->request->controller()); $actionname = strtolower($this->request->action()); // 获取Token $token = $this->request->server('HTTP_INTERNALTOKEN', $this->request->request('internaltoken', \think\Cookie::get('internaltoken'))); $path = str_replace('.', '/', $controllername) . '/' . $actionname; // 设置当前请求的URI $this->auth->setRequestUri($path); // 检测是否需要验证登录 if (!$this->auth->match($this->noNeedLogin)) { // 初始化内部端Token $this->auth->init($token); // 检测是否登录 if (!$this->auth->isLogin()) { $this->error("请先登录", null, 401); } // 判断是否需要验证权限 if (!$this->auth->match($this->noNeedRight)) { // 使用内部端权限规则校验 // if (!$this->auth->check($path)) { // $this->error(__('You have no permission'), null, 403); // } } } else { // 如果有传递token才验证是否登录状态 if ($token) { $this->auth->init($token); } } // 公共上传配置(保持与用户端一致) $upload = \app\common\model\Config::upload(); Hook::listen("upload_config_init", $upload); Config::set('upload', array_merge(Config::get('upload'), $upload)); // 加载当前控制器语言包 $this->loadlang($controllername); } /** * 刷新Token(重写父类方法) */ protected function token() { $token = $this->request->param('__token__'); // 使用内部端专用Token验证规则 if (!Validate::make()->check(['__token__' => $token], [ '__token__' => 'require|token:store' ])) { $this->error(__('Token verification error'), [ '__token__' => $this->request->token('internal') ]); } // 刷新内部端专用Token $this->request->token('internal', 'md5'); } }
最后在application/config.php 里面增加一个配置
'auth_groups' => [ 'api' => [ 'model' => \app\common\model\User::class, 'login_field' => 'mobile',//['username', 'email', 'mobile'], 'keeptime' => 2592000, ], 'internal' => [ 'model' => \app\common\model\InternalUser::class, 'login_field' => 'username', 'keeptime' => 2592000, ] ],
这样,在请求api和internal的接口时,就可以验证各自的token