You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Symfony6+React调用API报401未授权,Postman正常前端异常求助

问题:Symfony 6 + React 前端调用API返回401 Unauthorized,Postman测试正常

使用Symfony 6和React.js开发时,获取用户购物车数据的API在Postman中测试正常,但前端调用时抛出错误:

401 Unauthorized : A security token is required but the token storage is empty.

此前尝试使用#[CurrentUser]注解也遇到类似问题:Postman正常但前端提示用户未找到,怀疑问题出在security.yaml配置上。


API控制器代码

class PanierController extends AbstractController
{
    public function __construct(TokenStorageInterface $tokenStorage) {
        $this->tokenStorage = $tokenStorage;
    }

    #[Route('/api/get_panier', name: 'get-panier', methods: ['GET','POST'])]
    public function getPanierContents(TokenInterface $token, Request $request): Response
    {
        $token = $this->tokenStorage->getToken();
        if (!$token) {
            return $this->json(['message' => 'Token not found'], Response::HTTP_UNAUTHORIZED);
        }

        $user = $token->getUser();
        if (!$user || !$user instanceof UserInterface) {
            $this->logger->error("User not found");
            return $this->json(['message' => 'User not found'], Response::HTTP_NOT_FOUND);
        }
        error_log("User found: " . $user->getUserIdentifier());

        $panier = $user->getPanier();

        if (!$panier) {
            return $this->json(['message' => 'Panier not found'], Response::HTTP_NOT_FOUND);
        }

        $billets = $panier->getBillets();

        // Convert billets collection to array
        $billetsArray = [];
        foreach ($billets as $billet) {
            $billetsArray[] = [
                'id' => $billet->getId(),
                'date_debut_evenementt'=>$billet->getDateDebutEvenement(),
                // Add other properties you want to include
            ];
        }
        // $serializedContents = $serializer->serialize($contents, 'json', ['groups' => 'billet']);

        return $this->json($billetsArray);
    
}

security.yaml配置

app_user_provider:
  entity:
    class: App\Entity\Users
    property: email

firewalls:
  dev:
    pattern: ^/(_(profiler|wdt)|css|images|js)/
    security: false
  main:
    lazy: true
    provider: app_user_provider
    custom_authenticator: App\Security\UsersAuthenticator
   
    logout:
      path: app_logout
      target: connexion_page
    
  login:
      stateless: true
      json_login:
          check_path: /api/login
          username_path: email
          password_path: password
          success_handler: lexik_jwt_authentication.handler.authentication_success
          failure_handler: lexik_jwt_authentication.handler.authentication_failure
    
  api:
      pattern:   ^/api
      stateless: true
      jwt: ~

React请求代码

export const fetchPanierData = (userId) => {
  return async (dispatch) => {
    try {
      const token = getToken();
      if (!token) {
        throw new Error("No token found");
      }
      const response = await axios.get(`${BASE_URL}/api/get_panier`, {
        headers: {
          'Authorization': `Bearer ${token}`,
          
        },
      });
      dispatch({ type: "FETCH_PANIER_SUCCESS", payload: response.data });
    } catch (error) {
      dispatch({
        type: "FETCH_PANIER_FAILURE",
        payload: error.response ? error.response.data : error.message,
      });
    }
  };
};

解决方案

1. 调整防火墙匹配顺序

Symfony防火墙是从上到下匹配请求的,原配置中main防火墙在api防火墙之前,main使用会话式认证,而前端请求是无状态的JWT认证,导致请求先匹配到main防火墙后无法获取会话token,返回401。

将login和api防火墙移到main之前,确保/api开头的请求优先匹配JWT认证的防火墙:

firewalls:
  dev:
    pattern: ^/(_(profiler|wdt)|css|images|js)/
    security: false
  login:
      stateless: true
      json_login:
          check_path: /api/login
          username_path: email
          password_path: password
          success_handler: lexik_jwt_authentication.handler.authentication_success
          failure_handler: lexik_jwt_authentication.handler.authentication_failure
  api:
      pattern:   ^/api
      stateless: true
      jwt: ~
  main:
    lazy: true
    provider: app_user_provider
    custom_authenticator: App\Security\UsersAuthenticator
   
    logout:
      path: app_logout
      target: connexion_page

2. 简化控制器代码(可选)

JWT认证生效后,Symfony会自动解析token并注入当前用户,无需手动从TokenStorage获取:

#[Route('/api/get_panier', name: 'get-panier', methods: ['GET'])]
public function getPanierContents(UserInterface $user): Response
{
    $panier = $user->getPanier();

    if (!$panier) {
        return $this->json(['message' => 'Panier not found'], Response::HTTP_NOT_FOUND);
    }

    $billets = $panier->getBillets();
    $billetsArray = [];
    foreach ($billets as $billet) {
        $billetsArray[] = [
            'id' => $billet->getId(),
            'date_debut_evenementt'=>$billet->getDateDebutEvenement(),
        ];
    }

    return $this->json($billetsArray);
}

3. 验证前端请求

确认getToken()函数正确获取登录时存储的JWT,且请求头Authorization格式正确(Bearer 后需带空格,无多余字符),可在请求前打印token验证有效性。


内容的提问来源于stack exchange,提问作者Omnia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 01:24:51