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

使用JWT与Express.js实现认证失败及重定向异常问题

问题描述

我正在开发一个Web应用的认证模块,使用运行在Docker容器中的MongoDB实例,后端采用Node.js和Express.js。

登录接口代码

app.post("/api/auth/signin", async (req, res) => {
  const username = req.body.username;
  const password = req.body.password;

  const userRequested = await db.collection("users").findOne({username:username});

  if (userRequested == null) {
    res.status(401).json({
      message: "Requested user does not exist",
    });
    res.json(username);
  } else if (username == null || password == null) {
    res.status(401).json({
      message: "Null username or password are not allowed",
    });
  } else {
    if (userRequested.password === password) {

      jwt.sign({ user: userRequested }, "secretkey", (err, token) => {
        res.redirect("/api/budget/whoami");
      });
    } else {
      res.status(401).send({
        message: "Wrong credentials",
      });
    }
  }
});

当用户输入正确凭证时,会重定向至/api/budget/whoami。

身份验证接口代码

app.get("/api/budget/whoami", (req, res, next) => {
    //res.json(req.headers["authorization"]);
    const bearerHeader = req.headers["authorization"];

    if (typeof bearerHeader !== undefined) {
      const bearerToken = bearerHeader.split(" ")[1];
      req.token = bearerToken;
      next();
    } else {
      res.sendStatus(403);
      res.json(req.headers["authorization"])
    }
  },
  (req, res) => {
    jwt.verify(req.token, "secretkey", (err, authData) => {
      if (err) {
        res.sendStatus(403);
      } else {
        res.status(201);
        res.json({ authData });
      }
    });
  }
);

测试表单代码

<html>
    <head>
        <title>Ajax</title>
        <script src="assets/js/app.js" defer></script>
        <link rel="stylesheet" href="assets/css/style.css"/>
    </head>
    <body>
        <form id="loginForm" action="/api/auth/signin" method="post">
            <label for="username">Username:</label>
            <input type="text" name="username" id="username" />
            <label for="password">Password:</label>
            <input type="password" name="password" id="password" />
            <input type="submit" value="login" />
        </form>
    </body>
</html>

问题

即使输入正确凭证,重定向后的响应仍是403 Forbidden,希望重定向页面能返回包含用户信息的JSON文档,无法理解该错误。

补充说明:已将"undefined"改为undefined,并使用了相同的签名密钥,但现在提交凭证后重定向未执行,页面无限加载。


问题原因与解决方法

核心问题1:重定向未携带JWT Token

你在登录接口生成了JWT Token,但直接用res.redirect跳转时,浏览器的重定向请求不会自动带上这个Token。而/api/budget/whoami接口需要从请求头的authorization字段获取Token,自然拿不到,就返回403。而且你现在的代码里,生成Token的回调函数只做了重定向,根本没把Token传给前端——浏览器完全不知道Token存在,更别说带到重定向请求里了。

核心问题2:登录接口逻辑顺序错误

你先判断userRequested == null,之后才判断username == null || password == null。如果用户没输入用户名或密码,userRequested会是null,直接触发第一个判断返回401,第二个判断永远不会执行,逻辑顺序搞反了。

核心问题3:多次响应导致无限加载

比如在第一个判断里,你先res.status(401).json(...),又调用res.json(username)——一个请求只能有一次响应,多次调用会导致Express内部出错,这也是页面无限加载的原因之一。

解决步骤

  1. 调整登录接口逻辑顺序,返回Token而非重定向
app.post("/api/auth/signin", async (req, res) => {
  const username = req.body.username;
  const password = req.body.password;

  // 先判断用户名/密码是否为空
  if (!username || !password) {
    return res.status(401).json({
      message: "Null username or password are not allowed",
    });
  }

  const userRequested = await db.collection("users").findOne({username: username});

  if (!userRequested) {
    return res.status(401).json({
      message: "Requested user does not exist",
    });
  }

  if (userRequested.password === password) {
    jwt.sign({ user: userRequested }, "secretkey", (err, token) => {
      if (err) {
        return res.status(500).json({message: "Token generation failed"});
      }
      // 返回Token给前端,由前端处理后续请求
      res.json({
        token: token,
        redirectUrl: "/api/budget/whoami"
      });
    });
  } else {
    return res.status(401).json({
      message: "Wrong credentials",
    });
  }
});
  1. 修改前端表单提交逻辑,手动携带Token请求接口
// assets/js/app.js
document.getElementById('loginForm').addEventListener('submit', async (e) => {
  e.preventDefault(); // 阻止表单默认跳转

  const username = document.getElementById('username').value;
  const password = document.getElementById('password').value;

  const response = await fetch('/api/auth/signin', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({username, password})
  });

  const data = await response.json();
  if (data.token) {
    // 存储Token到本地
    localStorage.setItem('authToken', data.token);
    // 手动请求whoami接口并在请求头带上Token
    const whoamiResponse = await fetch('/api/budget/whoami', {
      headers: {
        'Authorization': `Bearer ${data.token}`
      }
    });
    const userData = await whoamiResponse.json();
    console.log(userData);
    // 也可以跳转到指定页面展示用户信息
    // window.location.href = '/user-profile';
  } else {
    alert(data.message);
  }
});
  1. 修复whoami接口的响应问题
app.get("/api/budget/whoami", (req, res, next) => {
    const bearerHeader = req.headers["authorization"];

    if (typeof bearerHeader !== 'undefined') {
      const bearerToken = bearerHeader.split(" ")[1];
      req.token = bearerToken;
      next();
    } else {
      // 只返回一次响应
      return res.sendStatus(403);
    }
  },
  (req, res) => {
    jwt.verify(req.token, "secretkey", (err, authData) => {
      if (err) {
        return res.sendStatus(403);
      } else {
        res.status(200).json({ authData }); // 201用于创建资源,这里用200更合适
      }
    });
  }
);

为什么不能用重定向?

重定向是浏览器发起的新GET请求,没办法自动把登录接口生成的Token带到请求头里。正确的流程应该是:登录接口返回Token → 前端保存Token → 后续请求手动在请求头里带上Token。

内容的提问来源于stack exchange,提问作者Eminent Emperor Penguin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 12:54:54