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($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",必须在部署方法列表版本之前完成:
- 在
fa_auth_rule表手动 INSERT 或通过 FastAdmin CRUD 生成器创建规则节点(name = 'my_task/recall') - 在 FastAdmin 后台 → 权限管理 → 角色管理,将该节点授权给对应角色
- 此后再把
recall从$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 层所有权校验。