Laravel Sanctum通用路由实现:支持登录/未登录用户数据区分
问题描述
我使用Laravel+React+Sanctum开发应用,需要设置通用路由,允许登录与未登录用户访问,并根据用户认证状态展示不同内容。目前遇到的问题是:只有在路由上添加auth::sanctum中间件时,才能通过Auth::check()判断用户是否登录,或通过Auth::user()获取当前登录用户信息。我尝试过$request->user('sanctum'),还自定义了中间件,但这些方案都无效。想在控制器方法中判断用户登录状态,并在用户登录时获取用户数据,请问该如何实现?现有方案为何无法生效?
补充信息
- 不使用任何中间件时,
Auth::user()返回null,Auth::check()返回false。 - 采用HttpOnly Cookie实现登录,后端登录代码如下:
public function login(Request $request) { if (!Auth::attempt($request->only('email', 'password'))) { return response([ 'error' => 'Invalid Credentials' ], Response::HTTP_UNAUTHORIZED); } $user = Auth::user(); $token = $user->createToken('token')->plainTextToken; $cookie = cookie('jwt', $token, 60 * 24); return response([ 'username' => $user->username ])->withCookie($cookie); }
- React端相关配置与代码:
- index.js中axios配置:
axios.defaults.baseURL = "http://localhost/api/"; axios.defaults.withCredentials = true;
- login.jsx登录逻辑:
const submit = async (value) => { setInProgress(true); await axios .post("login", { email: value.email, password: value.password, }) .then((data) => { const dataJSON = data; setInProgress(false); UserStore.storeUser({ username: dataJSON.data.username, avatar: dataJSON.data.avatar, }); setRedirect(true); }) .catch((err) => { setTimeout(() => { setInProgress(false); setCantLogin(true); }, 200); }); };
- App.jsx初始化逻辑:
useEffect(() => { i18n.changeLanguage("pl"); axios.get("/user").then((data) => { const dataJSON = data; UserStore.storeUser({ username: dataJSON.data.username, avatar: dataJSON.data.avatar ? dataJSON.data.avatar : null, }); }); });
- 请求
http://localhost/sanctum/csrf-cookie时出现循环,页面无法正常工作,是否所有页面都需要执行该请求? - 登录后已请求该端点,XSRF Cookie已存在,但问题仍未解决,更新后的登录代码如下:
const submit = async (value) => { setInProgress(true); await axios .post("login", { email: value.email, password: value.password, }) .then((data) => { const dataJSON = data; setInProgress(false); UserStore.storeUser({ username: dataJSON.data.username, avatar: dataJSON.data.avatar, }); const url = "/sanctum/csrf-cookie"; fetch(url).then(() => { alert("udało się"); }); setRedirect(true); }) .catch((err) => { setTimeout(() => { setInProgress(false); setCantLogin(true); }, 200); }); };
解决方案
现有方案无效原因
- 自定义中间件逻辑错误:你的自定义中间件仅检查
$request->bearerToken(),但你用HttpOnly Cookie存储token,前端并未将token放在请求头的Authorization字段中,导致$request->bearerToken()无法获取到值,自然无法设置登录用户。 $request->user('sanctum')未生效:未启用Sanctum的Cookie认证中间件,Sanctum无法自动从Cookie中读取token并解析用户信息。- CSRF请求时机错误:你在登录成功后才请求
sanctum/csrf-cookie,但CSRF Cookie需要在发起第一个POST请求(如登录)前获取,否则登录请求会因CSRF校验失败被拦截;且无需所有页面都请求该端点,仅需在首次发起有状态请求前调用一次即可。
正确实现步骤
1. 配置Sanctum Cookie认证中间件
在app/Http/Kernel.php的api中间件组中添加Sanctum的状态请求中间件,该中间件会自动处理Cookie中的Sanctum token:
protected $middlewareGroups = [ 'api' => [ \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, ], ];
2. 实现宽松的用户状态判断
方法一:控制器内直接判断
无需给路由添加强制认证中间件,直接在控制器方法中通过Sanctum guard获取用户:
public function index(Request $request) { // 通过sanctum guard尝试获取用户,不会强制拦截未登录请求 $user = Auth::guard('sanctum')->user(); if ($user) { // 已登录逻辑:返回用户信息或对应内容 return response()->json([ 'authenticated' => true, 'user' => $user->only('id', 'username', 'avatar') ]); } else { // 未登录逻辑:返回公共内容 return response()->json([ 'authenticated' => false, 'content' => '公共内容' ]); } }
方法二:自定义宽松认证中间件
若多个控制器需要此逻辑,可编写中间件自动绑定Sanctum用户到默认Auth:
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; class AuthenticateSanctumOptional { public function handle(Request $request, Closure $next) { // 尝试用sanctum guard认证用户,不强制拦截未登录请求 if ($user = Auth::guard('sanctum')->user()) { Auth::setUser($user); } return $next($request); } }
在Kernel.php中注册该中间件:
protected $routeMiddleware = [ // ...其他中间件 'auth.sanctum.optional' => \App\Http\Middleware\AuthenticateSanctumOptional::class, ];
给需要的路由添加该中间件:
Route::get('/user', [UserController::class, 'show'])->middleware('auth.sanctum.optional');
之后即可在控制器中直接使用Auth::check()和Auth::user()判断用户状态。
3. 修复前端CSRF请求逻辑
将sanctum/csrf-cookie请求放在登录请求之前:
const submit = async (value) => { setInProgress(true); try { // 先获取CSRF Cookie await axios.get('/sanctum/csrf-cookie'); // 再发起登录请求 const data = await axios.post("login", { email: value.email, password: value.password, }); UserStore.storeUser({ username: data.data.username, avatar: data.data.avatar, }); setInProgress(false); setRedirect(true); } catch (err) { setTimeout(() => { setInProgress(false); setCantLogin(true); }, 200); } };
无需在所有页面调用sanctum/csrf-cookie,浏览器会自动保存CSRF Cookie,后续请求会自动携带。
4. 检查Laravel环境配置
确保.env文件中相关配置正确:
SESSION_DRIVER=cookie SANCTUM_STATEFUL_DOMAINS=localhost:3000,localhost # 对应React端口和后端域名 SESSION_DOMAIN=localhost # 前后端同域名时设置为localhost
内容的提问来源于stack exchange,提问作者Sdsdsd
相关产品推荐
相关产品推荐

