返回博客

FastAdmin $noNeedRight 从 ['*'] 改成方法列表,竟然让所有人都 403

某天给一个后台管理系统的新控制器做 code review,审查者发现 $noNeedRight = ['*'] 权限"过于宽松",建议把它收紧:只让部分只读方法跳过权限检查,其余写操作走细粒度管控。听起来非常合理。结果部署之后,那几个"被管控"的方法对所有登录用户返回 {"code":403,"msg":"你没有权限访问"}——包括超级管理员。

现象

  • 控制器原来:protected $noNeedRight = ['*'];
  • 审查建议改为:protected $noNeedRight = ['getList', 'getDetail', 'previewTarget'];
  • 部署后:getList/getDetail 正常;createTask/recall/retryFailed 全部 403
  • Token 有效,其他控制器的接口完全正常
  • 登录账号是超级管理员角色
  • fa_auth_rule 表执行 SELECT * FROM fa_auth_rule WHERE name LIKE 'inbox_send_task/%' ——0 行

根因

FastAdmin(基于 ThinkPHP 5)的 Api 基类在每次请求时执行一个权限检查链:

$noNeedLogin → 是否需要登录
$noNeedRight → 是否需要检查权限节点
Auth::check($rule) → 在 fa_auth_rule 里找 controller/method 行,并验证当前角色是否被授权

$noNeedRight = ['*'] 的含义是:所有方法均跳过权限节点检查,只要登录就行。

$noNeedRight = ['getList', 'getDetail'] 的含义是:仅这几个方法跳过,其余方法走 Auth::check()

坑:Auth::check() 不只是「权限不足」,还会「节点不存在就 403」

Auth::check($rule) 的逻辑:先在 fa_auth_rule 里找 name = 'controller/method' 的行,找不到就视为「无此权限」,直接 403。

对于新创建的控制器,fa_auth_rule 里根本没有对应的规则行。不管是超管还是普通管理员,Auth::check() 都返回 false——因为"没有可授权的规则节点",而不是"有节点但你没权限"。

结论:把方法从 noNeedRight 里移除,实际上并不是"让它走权限管控",而是"让它撞上一个不存在的门"——所有人都进不去。

正解

方案 A:保留 ['*'],把授权逻辑下沉到 Service 层

FastAdmin 的权限节点是操作级("能不能创建任务"),不是记录级("只能撤回自己创建的任务")。对于需要所有权校验的场景,Service 层做数据归属检查更合适:

class MyTaskController extends Api
{
    protected $noNeedLogin = [];
    protected $noNeedRight = ['*'];  // ✅ 所有已登录管理员均可进入
 
    protected function requireAdminId(): string
    {
        $userInfo = isset($this->auth) ? $this->auth->getUserinfo() : null;
        $adminId = (string)($userInfo['id'] ?? '');
        if ($adminId === '') {
            $this->error('登录态异常,请重新登录', null, 401, 'json');
        }
        return $adminId;
    }
 
    public function recall()
    {
        $adminId = $this->requireAdminId();  // 401 fail-fast
        $id      = $this->request->post('id', '', 'trim');
        // Service 层检查:create_by 必须等于 $adminId(或超管跳过)
        $n = TaskService::recall($id, $adminId, false);
        $this->success('撤回成功', ['count' => $n]);
    }
}
// Service 层:记录级所有权校验
public static function recall($taskId, $recalledBy, $isSuperAdmin = false)
{
    $task = Db::name('my_task')->where('id', $taskId)->find();
    if (!$isSuperAdmin
        && !empty($task['create_by'])
        && (string)$task['create_by'] !== (string)$recalledBy) {
        throw new \think\Exception('无权撤回他人创建的任务');
    }
    // ... 执行撤回逻辑
}

方案 B:确实需要方法级权限管控?先把规则节点建好

若业务确实需要"只有被授权角色才能调用 recall",必须在部署方法列表版本之前完成:

  1. fa_auth_rule 表手动 INSERT 或通过 FastAdmin CRUD 生成器创建规则节点(name = 'my_task/recall'
  2. 在 FastAdmin 后台 → 权限管理 → 角色管理,将该节点授权给对应角色
  3. 此后再把 recall$noNeedRight 移除,才能生效
正解:先建规则节点,再收紧 noNeedRight

方法不在 $noNeedRight 里,不等于"受到管控"——它等于"尝试找一个不存在的授权门"。新控制器上线时,要么保持 ['*'] + Service 层所有权检查,要么先在 fa_auth_rule 建好规则节点并完成角色授权,再切换为方法列表。

排查速查

# 确认规则节点是否存在(将 my_task 替换为你的控制器 snake_case 名)
SELECT id, name, title FROM fa_auth_rule WHERE name LIKE 'my_task/%';
# 0 行 → 规则节点未建,方法列表模式必然全 403

一句话外卖

$noNeedRight 方法列表不是"细化权限",是"把这些方法扔进 Auth::check() 的黑洞"——新控制器在 fa_auth_rule 建好规则节点前,只能用 ['*'] + Service 层所有权校验。