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'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"); };
服务端用户信息存储解决方案
方案1:将用户信息存入HttpOnly Cookie
登录成功后,把用户信息序列化后存入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
相关产品推荐
相关产品推荐

