Loading

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

 

posted @ 2025-09-30 16:28  路闻man  阅读(8)  评论(0)    收藏  举报