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

Next.js登录后如何存储用户信息供服务端页面及操作访问

Next.js 服务端用户信息存储方案问题

我用Next.js搭配独立后端,采用JWT Access Token + Refresh Token的认证方案。已经实现了服务端动作ApiRequestHandler处理所有后端请求,也完成了登录的服务端动作开发。目前客户端可以通过Context存储登录接口返回的用户信息,但不知道如何在服务端存储该用户信息,以便在服务端页面与服务端动作中调用。


Login 组件代码

"use client";
import React from "react";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import Link from "next/link";

import { Button } from "@/components/ui/button";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { ChevronRight } from "lucide-react";
import { useRouter } from "next/navigation";
import { login } from "@/lib/actions";

const formSchema = z.object({
  email: z.string().email(),
  password: z
    .string()
    .min(6, { message: "Password must be at least 6 characters long" }),
});

const LoginForm = ({ setCurrentView }) => {
  const router = useRouter();

  const form = useForm({
    resolver: zodResolver(formSchema),
    defaultValues: {
      email: "john@mail.com",
      password: "test123",
    },
  });

  async function onSubmit(values) {
    try {
      values.role = "MANAGER";
      const res = await login(values);
      // 客户端可将用户信息存入Context
      router.push("/manager");
    } catch (error) {
      console.log(error);
    }
  }

  return (
    <div className="rounded-lg bg-white shadow-sm w-10/12 lg:w-10/12 xl:w-6/12 max-w-[400px] border">
      <div className="p-6">
        <div className="mb-6 text-primary font-semibold text-xl">
          Evento Business
        </div>
        <Form {...form}>
          <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-3">
            <FormField
              control={form.control}
              name="email"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Please enter your email to continue</FormLabel>
                  <FormControl>
                    <Input placeholder="Type your email here..." {...field} />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />
            <FormField
              control={form.control}
              name="password"
              render={({ field }) => (
                <FormItem>
                  <FormLabel>Please enter the password</FormLabel>
                  <FormControl>
                    <Input
                      placeholder="Type your password here..."
                      {...field}
                    />
                  </FormControl>
                  <FormMessage />
                </FormItem>
              )}
            />
            <Button type="submit" className="w-full">
              Login
            </Button>
          </form>
        </Form>
        <div className="mt-4 text-center text-sm">
          Don&apos;t have an account?{" "}
          <Link href="#" className="underline">
            Create now!
          </Link>
        </div>
      </div>

      <div className="border-t h-16 flex items-center relative mt-12 p-6">
        <div className="absolute -top-3 bg-white left-4 px-2 font-medium">
          Or sign in as
        </div>

        <Button
          onClick={() => setCurrentView("user")}
          variant="ghost"
          className="hover:bg-inherit pl-0"
        >
          User
          <ChevronRight className="ml-2" />
        </Button>
      </div>
    </div>
  );
};

export default LoginForm;

ApiRequestHandler.js(服务端动作)

"use server";

import { cookies } from "next/headers";
import { logout } from "./actions";

const baseURL = process.env.NEXT_API_BASE_URL;

const ApiRequestHandler = async (
  url,
  method = "get",
  data = null,
  headers = {},
  retryCount = 1
) => {
  const cookieStore = cookies();
  let authorizationHeader = {};
  if (cookieStore.has("accessToken")) {
    const accessToken = cookieStore.get("accessToken").value;
    authorizationHeader = { Authorization: `Bearer ${accessToken}` };
  }

  const response = await fetch(`${baseURL + url}`, {
    method,
    headers: {
      "Content-Type": "application/json",
      ...headers,
      ...authorizationHeader,
    },
    body: data ? JSON.stringify(data) : null,
  });
  if (!response.ok) {
    if (response.status === 401 && retryCount > 0) {
      const refreshResponse = await refreshToken();
      if (refreshResponse.ok) {
        // 重试原请求,减少重试次数
        return ApiRequestHandler(url, method, data, headers, retryCount - 1);
      } else {
        // Token刷新失败,抛出错误
        throw new Error("Token refresh failed");
      }
    }
    // 处理其他错误响应
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  return response.json();
};

const refreshToken = async () => {
  const cookieStore = cookies();
  if (cookieStore.has("refreshToken")) {
    const refreshToken = cookieStore.get("refreshToken").value;

    const response = await fetch(`${baseURL}/auth/refresh`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ refreshToken }),
    });

    if (response.ok) {
      const jsonResponse = await response.json();
      cookieStore.set("accessToken", jsonResponse.data.accessToken, {
        secure: true,
        // 添加其他必要Cookie属性(HttpOnly, SameSite等)
      });
      cookieStore.set("refreshToken", jsonResponse.data.refreshToken, {
        secure: true,
        // 添加其他必要Cookie属性(HttpOnly, SameSite等)
      });
    }
    return response;
  } else {
    logout();
  }
};

export default ApiRequestHandler;

其他服务端动作代码

"use server";

import { cookies } from "next/headers";
import ApiRequestHandler from "./apiRequestHandler";
import { redirect } from "next/navigation";

export const login = async (values) => {
  const cookieStore = cookies();
  try {
    const res = await ApiRequestHandler("/auth/login", "post", values);
    // res返回令牌和用户信息,我想在服务端存储该用户信息
    cookieStore.set("accessToken", res.data.accessToken);
    cookieStore.set("refreshToken", res.data.refreshToken);
    return res.data;
  } catch (error) {
    throw new Error(error);
  }
};

export const logout = () => {
  const cookieStore = cookies();
  cookieStore.delete("accessToken");
  cookieStore.delete("refreshToken");
  redirect("/login");
};

服务端用户信息存储解决方案

登录成功后,把用户信息序列化后存入Cookie,设置安全属性防止XSS攻击。修改login动作:

export const login = async (values) => {
  const cookieStore = cookies();
  try {
    const res = await ApiRequestHandler("/auth/login", "post", values);
    // 存储令牌,添加安全属性
    cookieStore.set("accessToken", res.data.accessToken, {
      secure: process.env.NODE_ENV === "production",
      httpOnly: true,
      sameSite: "strict",
      path: "/",
    });
    cookieStore.set("refreshToken", res.data.refreshToken, {
      secure: process.env.NODE_ENV === "production",
      httpOnly: true,
      sameSite: "strict",
      path: "/",
    });
    // 存储用户信息到Cookie
    cookieStore.set("userInfo", JSON.stringify(res.data.user), {
      secure: process.env.NODE_ENV === "production",
      httpOnly: true,
      sameSite: "strict",
      path: "/",
      maxAge: 24 * 60 * 60, // 有效期和Refresh Token匹配
    });
    return res.data;
  } catch (error) {
    throw new Error(error);
  }
};

方案2:解析Access Token获取用户信息

如果JWT Token本身包含用户信息,可直接在服务端解析Token获取,无需额外存储。创建工具函数:

// @/lib/utils.js
import jwt from "jsonwebtoken";

export const getUserFromToken = () => {
  const cookieStore = cookies();
  const accessToken = cookieStore.get("accessToken")?.value;
  if (!accessToken) return null;
  
  try {
    // 使用后端相同的JWT密钥解析
    const decoded = jwt.verify(accessToken, process.env.JWT_SECRET);
    return decoded.user; // 假设Token包含user字段
  } catch (error) {
    // Token无效或过期,返回null
    return null;
  }
};

在服务端页面/动作中调用:

// 服务端页面示例
import { getUserFromToken } from "@/lib/utils";
import { redirect } from "next/navigation";

export default async function ManagerPage() {
  const user = getUserFromToken();
  if (!user) redirect("/login");
  
  return <div>欢迎,{user.name}</div>;
}

// 服务端动作示例
"use server";
import { getUserFromToken } from "@/lib/utils";

export const getManagerData = async () => {
  const user = getUserFromToken();
  if (!user) throw new Error("未授权");
  
  const data = await ApiRequestHandler(`/manager/${user.id}`);
  return data;
};

方案3:服务端组件直接读取用户信息

在App Router的服务端组件中,可直接结合上述方案获取用户信息:

// app/manager/page.js
import { getUserFromToken } from "@/lib/utils";
import { redirect } from "next/navigation";

export default async function ManagerPage() {
  const user = getUserFromToken();
  if (!user) redirect("/login");
  
  return (
    <div>
      <h1>管理员面板</h1>
      <p>当前用户:{user.email}</p>
    </div>
  );
}

注意事项

  • 存储用户信息到Cookie时,避免存入敏感数据,或加密后存储
  • 解析JWT时必须使用与后端一致的密钥,同时处理Token过期情况
  • 所有服务端操作都要校验用户权限,防止未授权访问

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 23:39:51