Laravel单点登录和多端认证源码如何编写?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1170个文字,预计阅读时间需要5分钟。
由于默认行为仅清除当前会话,不触及数据库中的+session+记录或其他设备的+token+,Laravel的+session+驱动(如file或redis)本身不提供跨设备会话管理能力。+logout()+函数仅删除当前请求绑定的session ID对应的数据,其他设备仍然可能持有旧的token进行访问。
常见错误现象:Auth::logout() 后,手机端或另一个浏览器仍能正常调用 API、访问受保护路由;后台查 personal_access_tokens 表发现旧 token 还在且未过期。
- 必须主动失效所有该用户的 token,不能只依赖 session 清除
- 如果用的是 Sanctum,要删
personal_access_tokens表中对应tokenable_id和tokenable_type的记录 - 如果用 Passport,得调用
$user->tokens()->delete(),而不是只删当前 token - 注意:删 token 后,正在使用的 API 请求会在下一次验证时失败(401),但不会“实时中断”已建立的连接
Sanctum 实现单点登录:覆盖 createToken() 并清理旧 token
Sanctum 默认允许一个用户无限创建 token,不做互斥控制。要实现单点登录,得在生成新 token 前,把该用户所有已有 token 干掉——这是最直接、副作用最小的做法。
使用场景:App 登录、网页登录共用同一套 Sanctum token 体系,要求新登录即作废旧登录。
- 在登录逻辑里,不要直接调用
$user->createToken('api-token') - 先执行
$user->currentAccessToken()->delete()(如果存在)或更稳妥地删全部:$user->tokens()->delete() - 再调用
$user->createToken('api-token')生成新 token - 注意
tokens()关系默认是HasApiTokenstrait 提供的,确保模型用了这个 trait - 性能影响极小,单次 DB delete 操作,无 N+1 问题
Passport 单点登录的关键:别漏掉 refresh_token 和 access_token 的级联清理
Passport 的 token 是成对存在的:access_token + refresh_token,且 refresh_token 可用于续签。只删 access_token,用户还能用 refresh_token 换新 access_token,等于没踢干净。
常见错误现象:调用 $user->tokens()->delete() 后,前端用旧 refresh_token 仍能成功请求 /oauth/token 拿到新 access_token。
- Passport 的
tokens()关系默认只关联oauth_access_tokens表,不自动连带删oauth_refresh_tokens - 必须手动删 refresh token:
DB::table('oauth_refresh_tokens')->where('access_token_id', $tokenId)->delete(),或更稳妥地按 user_id 批量删 - 推荐做法:用
$user->tokens()->with(['refreshToken'])加载后遍历删除,或直接两表联合 delete(需确认外键约束) - 兼容性注意:Laravel 10+ 的 Passport 11+ 中
refresh_token表结构有变更,字段名从access_token_id改为access_token_id(不变),但逻辑一致
Session 驱动下怎么让多端登出同步?别只信 Session::flush()
基于 session 的 Web 登录(比如 web guard),Session::flush() 或 Auth::logout() 只清当前请求的 session 数据,不影响其他设备的 session 文件或 redis key。要真正“踢人”,得从存储层下手。
使用场景:用户在电脑上点“退出登录”,希望手机网页也立刻失效。
- session 存 Redis 时,可用
Redis::keys("laravel_session*")扫描并匹配user_id字段(前提是 session 内存了 user_id)——但不推荐,性能差且不可靠 - 更稳的方式:在用户表加
last_login_at或session_version字段,中间件里每次请求都比对,不一致就强制登出 - 或者用缓存标记:登录时写
Cache::put("user:{$id}:session_version", $uuid, 3600),登出时Cache::forget(...),所有请求前校验该值是否匹配 session 里存的版本 - 关键点:session 本身无中心状态,单点登录必须引入额外状态存储,否则纯靠 session 驱动做不到真正同步
本文共计1170个文字,预计阅读时间需要5分钟。
由于默认行为仅清除当前会话,不触及数据库中的+session+记录或其他设备的+token+,Laravel的+session+驱动(如file或redis)本身不提供跨设备会话管理能力。+logout()+函数仅删除当前请求绑定的session ID对应的数据,其他设备仍然可能持有旧的token进行访问。
常见错误现象:Auth::logout() 后,手机端或另一个浏览器仍能正常调用 API、访问受保护路由;后台查 personal_access_tokens 表发现旧 token 还在且未过期。
- 必须主动失效所有该用户的 token,不能只依赖 session 清除
- 如果用的是 Sanctum,要删
personal_access_tokens表中对应tokenable_id和tokenable_type的记录 - 如果用 Passport,得调用
$user->tokens()->delete(),而不是只删当前 token - 注意:删 token 后,正在使用的 API 请求会在下一次验证时失败(401),但不会“实时中断”已建立的连接
Sanctum 实现单点登录:覆盖 createToken() 并清理旧 token
Sanctum 默认允许一个用户无限创建 token,不做互斥控制。要实现单点登录,得在生成新 token 前,把该用户所有已有 token 干掉——这是最直接、副作用最小的做法。
使用场景:App 登录、网页登录共用同一套 Sanctum token 体系,要求新登录即作废旧登录。
- 在登录逻辑里,不要直接调用
$user->createToken('api-token') - 先执行
$user->currentAccessToken()->delete()(如果存在)或更稳妥地删全部:$user->tokens()->delete() - 再调用
$user->createToken('api-token')生成新 token - 注意
tokens()关系默认是HasApiTokenstrait 提供的,确保模型用了这个 trait - 性能影响极小,单次 DB delete 操作,无 N+1 问题
Passport 单点登录的关键:别漏掉 refresh_token 和 access_token 的级联清理
Passport 的 token 是成对存在的:access_token + refresh_token,且 refresh_token 可用于续签。只删 access_token,用户还能用 refresh_token 换新 access_token,等于没踢干净。
常见错误现象:调用 $user->tokens()->delete() 后,前端用旧 refresh_token 仍能成功请求 /oauth/token 拿到新 access_token。
- Passport 的
tokens()关系默认只关联oauth_access_tokens表,不自动连带删oauth_refresh_tokens - 必须手动删 refresh token:
DB::table('oauth_refresh_tokens')->where('access_token_id', $tokenId)->delete(),或更稳妥地按 user_id 批量删 - 推荐做法:用
$user->tokens()->with(['refreshToken'])加载后遍历删除,或直接两表联合 delete(需确认外键约束) - 兼容性注意:Laravel 10+ 的 Passport 11+ 中
refresh_token表结构有变更,字段名从access_token_id改为access_token_id(不变),但逻辑一致
Session 驱动下怎么让多端登出同步?别只信 Session::flush()
基于 session 的 Web 登录(比如 web guard),Session::flush() 或 Auth::logout() 只清当前请求的 session 数据,不影响其他设备的 session 文件或 redis key。要真正“踢人”,得从存储层下手。
使用场景:用户在电脑上点“退出登录”,希望手机网页也立刻失效。
- session 存 Redis 时,可用
Redis::keys("laravel_session*")扫描并匹配user_id字段(前提是 session 内存了 user_id)——但不推荐,性能差且不可靠 - 更稳的方式:在用户表加
last_login_at或session_version字段,中间件里每次请求都比对,不一致就强制登出 - 或者用缓存标记:登录时写
Cache::put("user:{$id}:session_version", $uuid, 3600),登出时Cache::forget(...),所有请求前校验该值是否匹配 session 里存的版本 - 关键点:session 本身无中心状态,单点登录必须引入额外状态存储,否则纯靠 session 驱动做不到真正同步

