自定义 guard 需手动注册驱动、实现 userprovider 接口并严格匹配配置;漏步骤会导致静默 fallback 到 web、user() 返回 null;必须在服务提供者中调用 auth::extend(),provider 要实现 retrievebycredentials() 和 validatecredentials() 方法,且 guard 与 provider 配置名须完全一致。

自定义 Guard 不是改个配置就能跑通的事,核心在于你得自己写逻辑、注册驱动、显式调用——漏一步,Auth::guard('xxx') 就会静默 fallback 到 web,查错表、登不出、user() 返回 null 都是常态。
怎么注册自定义 Guard 驱动(Auth::extend)
必须在服务提供者(比如 AppServiceProvider 的 boot() 方法)里调用 Auth::extend(),不能只配 config/auth.php。这个闭包返回一个守卫实例,它要接收两个关键依赖:用户提供者和请求对象。
常见错误是直接 new 一个类却不传 $app['request'],导致守卫无法读取 header 或 cookie;或者 provider 创建失败(比如 Auth::createUserProvider($config['provider']) 找不到配置名),整个 guard 初始化就静默失败。
- 闭包参数必须包含
$app,$name,$config,否则 Laravel 不识别 -
$config['provider']必须和config/auth.php中providers下的键名完全一致(比如'admins') - 不要在闭包里硬编码模型路径,应由 provider 负责加载用户,guard 只做凭证提取和状态管理
自定义 UserProvider 必须实现哪几个方法
只要实现 Illuminate\Contracts\Auth\UserProvider 接口,Laravel 才认它是合法提供者。最常翻车的是 retrieveByCredentials() 和 validateCredentials() —— 前者负责“找人”,后者负责“验密”,两者缺一不可,且返回值类型严格:
-
retrieveByCredentials(array $credentials)必须返回Authenticatable实例或null,不能返回数组或 Eloquent Collection -
validateCredentials(Authenticatable $user, array $credentials)必须返回布尔值,且只能用Hash::check()验证密码,不能手写==或strcmp() - 如果从 Redis 或 LDAP 加载用户,
retrieveById()里别忘了处理缓存穿透,否则高并发下 DB 直接被打穿
Auth::guard('xxx')->attempt() 成功但 user() 为空
这不是密码错了,而是 guard 和 provider 没对齐,或者 session 没写进去。典型表现:登录接口返回 200,重定向后 Auth::guard('xxx')->check() 是 true,但 Auth::guard('xxx')->user() 是 null。
- 检查
config/auth.php中该 guard 的provider值是否拼写错误(比如写成'admin'却没配providers.admin) - 确认 provider 对应的 model 类确实实现了
Authenticatable接口,且$fillable包含用于登录的字段(如'api_key') - 如果你用了
Auth::login($user)而不是Auth::guard('xxx')->attempt(),必须显式传 guard 名:Auth::guard('xxx')->login($user),否则写进的是web的 session
中间件 auth:xxx,yyy 是“或”逻辑,但 session 会互相污染
逗号分隔多个 guard(如 middleware('auth:web,admin'))确实是短路“或”,但前提是它们底层不共享 session 存储 key。默认所有 session guard 都用 login_{guard}_{id} 这种格式,如果两个 guard 都用 session 驱动且没隔离 store,后登录的会覆盖前一个的 session 数据。
- 解决办法是在
config/session.php里为每个 guard 配独立prefix,比如'admin' => ['driver' => 'redis', 'prefix' => 'sess_admin_'] - 或者更稳妥:让 admin 守卫改用
token驱动,彻底避开 session 冲突 - 别指望
auth:web,admin能自动合并权限——它只管“有没有认证”,不校验角色或能力
真正麻烦的从来不是写代码,而是所有调用点都得带 guard 名:登录、登出、中间件、获取用户、甚至 intended() 跳转。少写一次 ->guard('xxx'),问题就藏在生产环境深夜三点的日志里。











