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

Next.js 14与Express.js间HttpOnly Cookie的设置、传递及页面保护

解决Next.js 14与Express后端的HttpOnly Cookie交互及页面保护问题

一、核心前提:HttpOnly Cookie的特性

HttpOnly Cookie不允许前端JS读取或修改,这是安全设计。浏览器会自动负责Cookie的存储、携带,你不需要在前端手动设置它,只需要确保请求配置正确,让浏览器能正常接收和发送Cookie。


二、第一步:后端完善CORS配置(关键!)

你当前的代码缺少CORS配置,这是前端无法接收Cookie的常见原因。在Express的server.js中添加CORS中间件:

const cors = require('cors');

// 配置CORS,允许前端域名携带凭证
app.use(cors({
  origin: 'http://localhost:3000', // 替换为你的Next.js前端地址,生产环境用实际域名
  credentials: true, // 必须开启,允许请求携带Cookie
}));

三、Next.js中接收Cookie(登录流程)

前端发起登录请求时,必须携带credentials: 'include',让浏览器保存后端返回的HttpOnly Cookie。以下是登录页面的示例代码:

// app/login/page.js(Client Component)
'use client';

export default function LoginPage() {
  const handleLogin = async (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    
    const res = await fetch('http://localhost:5000/api/v1/users/login', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded', // 匹配后端upload.none()的表单格式
      },
      body: new URLSearchParams({
        username: formData.get('username'),
        password: formData.get('password'),
      }),
      credentials: 'include', // 关键:允许浏览器接收并存储Cookie
    });

    if (!res.ok) {
      const error = await res.json();
      alert(error.message);
      return;
    }

    // 登录成功后跳转,浏览器已自动保存Cookie
    window.location.href = '/protected';
  };

  return (
    <form onSubmit={handleLogin}>
      <input name="username" placeholder="用户名" required />
      <input name="password" type="password" placeholder="密码" required />
      <button type="submit">登录</button>
    </form>
  );
}

四、Next.js向后端自动携带Cookie

所有需要授权的请求,都要添加credentials: 'include',浏览器会自动把存储的HttpOnly Cookie发送给后端:

// 示例:获取受保护数据(Client Component)
async function fetchProtectedData() {
  const res = await fetch('http://localhost:5000/api/v1/data', {
    credentials: 'include', // 自动携带Cookie
  });

  if (!res.ok) {
    // 未授权时跳转登录页
    window.location.href = '/login';
    return null;
  }

  return await res.json();
}

五、Next.js中保护特定页面

推荐使用Middleware实现全局路由保护,也可以在Server Component中单独验证,两种方式任选:

方式1:Middleware全局保护(推荐)

在app/middleware.js中编写中间件,拦截未授权请求:

import { NextResponse } from 'next/server';

export async function middleware(request) {
  const token = request.cookies.get('token')?.value;
  // 定义需要保护的路由
  const protectedRoutes = ['/protected', '/dashboard'];
  const isProtected = protectedRoutes.includes(request.nextUrl.pathname);

  // 未授权访问保护路由,重定向到登录页
  if (isProtected && !token) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  // 已登录状态访问登录页,重定向到首页
  if (request.nextUrl.pathname === '/login' && token) {
    // 额外验证token有效性,避免过期Cookie残留
    const verifyRes = await fetch('http://localhost:5000/api/v1/verify-token', {
      method: 'POST',
      body: JSON.stringify({ token }),
      headers: { 'Content-Type': 'application/json' },
    });

    if (verifyRes.ok) {
      return NextResponse.redirect(new URL('/', request.url));
    }
  }

  return NextResponse.next();
}

// 指定中间件作用的路由
export const config = {
  matcher: ['/login', '/protected/:path*', '/dashboard/:path*'],
};

需要在后端添加一个token验证接口(userController.js):

router.post('/verify-token', async (req, res) => {
  try {
    const token = req.cookies.token || req.body.token;
    if (!token) return res.status(401).json({ success: false, message: '无有效凭证' });

    // 验证JWT有效性
    const decoded = JWT.verify(token, secret);
    res.status(200).json({ success: true, user: decoded });
  } catch (err) {
    res.status(401).json({ success: false, message: '凭证无效或过期' });
  }
});

方式2:Server Component页面内验证

在需要保护的页面(如app/protected/page.js)中直接验证:

import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';

export default async function ProtectedPage() {
  const cookieStore = cookies();
  const token = cookieStore.get('token')?.value;

  if (!token) redirect('/login');

  // 验证token有效性
  const verifyRes = await fetch('http://localhost:5000/api/v1/verify-token', {
    method: 'POST',
    body: JSON.stringify({ token }),
    headers: { 'Content-Type': 'application/json' },
  });

  if (!verifyRes.ok) redirect('/login');

  const user = await verifyRes.json();

  return (
    <div>
      <h1>受保护页面</h1>
      <p>欢迎,用户ID:{user.user.id}</p>
    </div>
  );
}

六、生产环境注意事项

  1. 开启Cookie的secure: true:仅在HTTPS环境下传输Cookie,符合安全规范
  2. 调整sameSite属性:如果前后端是跨域名,设置sameSite: 'none'并配合secure: true;同域名用lax即可
  3. CORS的origin设置为生产环境的实际域名,不要用通配符*(会导致credentials失效)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 00:53:22