Symfony JSON 登录中 $user 为空但实际已认证的解决方案

13次阅读

Symfony JSON 登录中 $user 为空但实际已认证的解决方案

本文详解 symfony 6 中 json 登录时 `#[currentuser] ?user $user` 在前端调用(如 angular)下为 `NULL`,却在 insomnia 等工具中正常的问题根源与修复方法——关键在于安全配置中的 `check_path` 名称必须与路由 **name** 完全一致。

该问题表面看似是认证失败或 CORS 冲突,实则源于 Symfony 安全组件对「登录检查路径」的严格匹配机制。当使用 #[CurrentUser] 参数绑定时,Symfony 并非在任意 /login 请求中注入用户对象,而仅在 被识别为“认证检查端点”(即 check_path)的请求中 才完成用户身份解析与注入。

? 根本原因:check_path 与路由 name 不匹配

在 security.yaml 中,你配置了:

# config/packages/security.yaml security:     firewalls:         login:             pattern: ^/api/login             stateless: true             json_login:                 check_path: login   # ← 关键:此处值必须是路由的 "name",而非 path!                 username_path: username                 password_path: password                 success_handler: null                 failure_handler: null

而你的控制器定义却是:

#[Route('/login', name: 'api_login')] // ❌ name 是 'api_login',但 check_path 设为 'login' public function login(#[CurrentUser] ?User $user): jsonResponse

这导致 Symfony 在处理 POST /login 请求时:

  • ✅ 正确触发 json_login 认证流程(所以凭据校验成功、session/cookie 无关、Token 生成逻辑可执行);
  • ❌ 却未将该请求识别为 check_path,因此跳过 User 对象的自动注入逻辑;
  • ⚠️ 最终 #[CurrentUser] 解析为 null,但后续 $user->getUserIdentifier() 调用未加防护,引发 php 致命错误。

? 提示:Insomnia 可能“偶然”工作,是因为你曾用 /api/login 测试过(匹配原始文档路径),或缓存了有效会话/Token;而 Angular 发起的是全新 /login 请求,暴露出配置不一致问题。

✅ 正确配置方式(两处必须统一)

1. 修改路由 name 以匹配 check_path

// src/Controller/LoginController.php #[Route('/login', name: 'login')] // ✅ name 改为 'login',与 security.yaml 中 check_path 一致 public function login(#[CurrentUser] ?User $user): JsonResponse {     if ($user === null) {         return $this->json([             'message' => 'Authentication failed or not authenticated.',         ], JsonResponse::HTTP_UNAUTHORIZED);     }      // 此时 $user 已安全实例化     $token = bin2hex(random_bytes(32)); // 示例 Token 生成      return $this->json([         'user' => $user->getUserIdentifier(),         'token' => $token,     ]); }

2. 确保 security.yaml 中 pattern 覆盖该路径(推荐显式声明)

# config/packages/security.yaml security:     firewalls:         main:             # ... 其他配置         login:             pattern: ^/login$     # ✅ 明确匹配 /login(结尾 $ 防止误匹配 /login/foo)             stateless: true             json_login:                 check_path: login   # ✅ 与路由 name 一致                 username_path: username                 password_path: password

? 补充注意事项

  • 不要依赖 pattern: ^/api/login 同时处理 /login:正则不匹配则防火墙不生效,json_login 不触发。
  • CORS 订阅器无需修改:你当前的 CorsSubscriber 逻辑正确(响应头添加),但注意:预检请求(OPTIONS)不会触发 json_login,因此确保前端 Content-Type: application/json 且无非法 header,避免触发预检失败。
  • 始终防御性判空:即使修复后,也建议保留 if ($user === null) 检查,作为安全兜底:
    if (!$user instanceof User) {     throw new accessDeniedException('Invalid user context.'); }
  • 调试技巧:启用 Symfony Profiler 或在日志中添加:
    $this->logger->info('Current user: {user}', ['user' => $user?->getUserIdentifier() ?? 'null']);

✅ 总结

项目 错误配置 正确配置
security.yaml 中 check_path ‘api_login’ ‘login’
路由注解 name 属性 name: ‘api_login’ name: ‘login’
防火墙 pattern ^/api/login ^/login$

只要 check_path 值与 #[Route(…, name: ‘xxx’)] 中的 xxx 完全相同,Symfony 才会在该请求中完成完整的认证上下文构建,#[CurrentUser] 才能可靠注入已认证的 User 实例。这是 Symfony 安全机制的设计契约,而非 bug —— 理解并遵守它,即可彻底解决 $user 为 null 的“诡异行为”。

text=ZqhQzanResources