应保留authenticatesusers trait默认行为,在控制器中覆盖username()、guard()、sendfailedloginresponse()等钩子方法;多guard登录需复用同一控制器并传参区分;formrequest中禁用auth::user()而用$this->user();中间件必须显式声明guard名。

AuthenticatesUsers trait 不该直接改
看到有人在 AuthenticatesUsers trait 里硬改 login() 或 attemptLogin(),这是危险操作。Laravel 框架更新时会覆盖 vendor 文件,你的修改直接丢失;更严重的是,它破坏了契约一致性——所有 guard 共享同一套登录流程逻辑,但你只改了其中一处,其他 guard(比如 admin、instructor)就可能跳过你加的校验。
真正该做的,是保留 trait 的默认行为,只在控制器里覆盖关键方法:
-
username():改登录字段名(如用phone替代email),返回字符串即可 -
guard():返回Auth::guard('admin')这类实例,控制本次请求走哪个 guard -
sendFailedLoginResponse():自定义失败响应(比如统一返回 JSON 而非重定向)
这些方法都是 Laravel 预留的钩子,不碰 vendor,升级安全,且每个 guard 可独立定制。
多 guard 登录不能靠复制粘贴 Controller
为 student 写一个 StudentLoginController,再为 instructor 复制一份改成 InstructorLoginController,表面看清晰,实则埋雷:两份代码里 attempt() 调用、状态检查(如是否被禁用)、会话清理、跳转路由全部重复。一旦要加双因素验证或登录失败锁定,就得同步改两处,漏一次就是线上漏洞。
正确做法是复用一个控制器,靠参数区分 guard:
- 路由中显式传 guard 名:
Route::post('/login/{guard}', [LoginController::class, 'store']) - 控制器里接收
$guard参数,传给私有方法attemptLogin(Request $request, string $guard) - 该方法内统一调用
Auth::guard($guard)->attempt(...),并检查对应模型的status字段(不同 guard 的用户模型字段可能不同,所以需传入$userClass和$deactivatedStatus)
这样新增一个 parent guard,只需加一条路由和配置 provider,不用动任何业务逻辑。
FormRequest 里别写 Auth::user()
在 StoreInfoRequest 的 rules() 方法里直接调用 Auth::user() 是常见错误。此时请求尚未通过中间件,Auth::user() 必然返回 null,导致规则失效(比如本该限制“仅本人可编辑”,结果谁都可改)。
FormRequest 生命周期早于认证中间件执行,可靠的方式只有两种:
- 用
$this->user():Laravel 自动注入当前认证用户(前提是已通过 auth 中间件) - 用
$this->route('id')获取 URL 参数,配合Rule::exists('posts')->where('user_id', $this->user()->id)做关联校验
如果这个 FormRequest 用于未登录场景(如注册),那 $this->user() 就不该出现——得靠 authorize() 返回 false 提前拦截。
中间件顺序决定认证是否生效
在 LoginController 构造函数里写 $this->middleware('guest:admin'),看似指定了 guard,其实没用——guest 中间件只检查「当前未登录」,并不自动切换 guard 上下文。真正起作用的是路由定义里的 guard 显式声明:
- 错的写法:
Route::get('/admin/login', [LoginController::class, 'showLoginForm'])->middleware('guest')→ 默认走webguard - 对的写法:
Route::get('/admin/login', [LoginController::class, 'showLoginForm'])->middleware('guest:admin')→ 强制用adminguard 判断是否已登录
同样,auth:admin 中间件也必须显式声明 guard,否则 logout 后刷新页面仍可能被 web guard 的 session 拦住——因为没指定 guard 时,Laravel 默认用 defaults.guard 配置项,也就是 web。
Guard 名不是字符串拼接出来的,是 config/auth.php 里 guards 数组的键名,拼错一个字母,整个认证链就断了,而且没有任何报错提示,只能 debug Auth::guard('xxx') 返回 null 才能发现。











