You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel:防止用户修改其他用户的待办事项(Todo)

优化Laravel待办事项权限验证的几种方案

你遇到的问题核心在于:Auth::user()->todos 返回的是Eloquent集合(因为hasManyThrough关联的是多条记录),所以直接访问->id会报错,而取first()->id的逻辑完全不合理——它只检查了你第一个待办事项的清单ID和当前待办的清单ID是否一致,根本无法确保当前待办事项属于你。

下面提供几种从简单到优雅的解决方案,覆盖不同场景的需求:

方案1:直接通过关联关系验证(快速实现)

既然Todo属于Todolist,而Todolist属于User,我们可以直接验证当前待办事项的所属清单的用户ID是否等于当前登录用户ID:

public function edit(Todo $todo) {
    // 检查当前todo的todolist所属用户是否是当前登录用户
    if ($todo->todolist->user_id === Auth::id()) {
        return view('todo/edit', ['todo' => $todo]);
    }
    abort(403, 'Unauthorized action.');
}

这个方案的优点是逻辑直接、代码简洁,不需要加载用户所有的待办事项,只需要额外查询一次todolist关联(Laravel的延迟加载会自动处理)。

方案2:使用关联查询验证(性能更优)

如果担心延迟加载的额外查询,可以用whereHas直接在数据库层面做验证,避免加载不必要的模型:

public function edit(Todo $todo) {
    $isAuthorized = Auth::user()->todos()->where('todos.id', $todo->id)->exists();
    if ($isAuthorized) {
        return view('todo/edit', ['todo' => $todo]);
    }
    abort(403, 'Unauthorized action.');
}

这个方法直接在数据库中查询当前用户是否拥有该待办事项,返回的是布尔值,性能更优,尤其是用户待办事项较多的时候。

方案3:使用Laravel策略(推荐,适合中大型项目)

Laravel的**Policy(策略)**是处理模型权限的官方推荐方式,它能把权限逻辑从控制器中剥离出来,让代码更整洁、可复用。

步骤1:生成Todo策略

运行Artisan命令生成策略文件:

php artisan make:policy TodoPolicy --model=Todo

步骤2:编写策略逻辑

打开app/Policies/TodoPolicy.php,添加view、update、delete方法的权限验证逻辑:

namespace App\Policies;

use App\Models\User;
use App\Models\Todo;

class TodoPolicy
{
    /**
     * 验证用户是否能查看待办事项
     */
    public function view(User $user, Todo $todo): bool
    {
        return $user->id === $todo->todolist->user_id;
    }

    /**
     * 验证用户是否能更新待办事项
     */
    public function update(User $user, Todo $todo): bool
    {
        // 复用view的逻辑,因为编辑权限和查看权限通常一致
        return $this->view($user, $todo);
    }

    /**
     * 验证用户是否能删除待办事项
     */
    public function delete(User $user, Todo $todo): bool
    {
        return $this->view($user, $todo);
    }
}

步骤3:注册策略

打开app/Providers/AuthServiceProvider.php,在$policies数组中注册Todo策略:

protected $policies = [
    \App\Models\Todo::class => \App\Policies\TodoPolicy::class,
];

步骤4:在控制器中使用策略

现在你的控制器方法可以简化成这样,Laravel会自动帮你处理权限验证:

public function edit(Todo $todo) {
    // 自动调用TodoPolicy的update方法验证权限,不通过则抛出403
    $this->authorize('update', $todo);
    return view('todo/edit', ['todo' => $todo]);
}

// 同理,查看和删除方法也可以这样写:
public function show(Todo $todo) {
    $this->authorize('view', $todo);
    return view('todo/show', ['todo' => $todo]);
}

public function destroy(Todo $todo) {
    $this->authorize('delete', $todo);
    $todo->delete();
    return redirect()->route('todos.index');
}

方案4:全局路由模型绑定约束(最高级,自动验证)

如果希望在路由解析阶段就自动验证权限,不需要在每个控制器方法中写代码,可以使用路由模型绑定的全局约束:

打开app/Providers/RouteServiceProvider.php,在boot方法中添加:

public function boot()
{
    parent::boot();

    // 绑定todo路由参数时自动验证权限
    Route::bind('todo', function ($value) {
        $todo = Todo::find($value);
        
        // 如果todo不存在,或者不属于当前用户,直接返回403
        if (!$todo || $todo->todolist->user_id !== Auth::id()) {
            abort(403, 'Unauthorized action.');
        }
        
        return $todo;
    });
}

这样,当用户访问类似/todos/1/edit的路由时,如果该todo不属于当前用户,会直接抛出403错误,你的控制器方法可以完全不需要权限判断:

public function edit(Todo $todo) {
    return view('todo/edit', ['todo' => $todo]);
}

总结

  • 小项目或者快速实现:用方案1或方案2
  • 中大型项目,追求代码整洁和可维护性:用方案3(策略)
  • 希望全局统一处理权限,减少控制器代码:用方案4(路由绑定约束)

内容的提问来源于stack exchange,提问作者wiwa1978

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 07:37:47