NextJS服务端组件GET请求未携带Django会话Cookie问题排查
我用Next.js做前端、Django做后端开发时碰到一个问题:服务端组件发送获取Post数据的GET请求时,不会携带Cookie,导致Django后端拿不到sessionid,request.user显示为AnonymousUser;但客户端组件触发的登出GET请求却能正常带Cookie,后端也能识别用户。
我已经用django-cors-headers处理跨域,试过设置Django的SESSION_COOKIE_SAMESITE=None,但问题还是没解决。
核心原因
Next.js的服务端组件(Server Component)运行在Node.js服务器环境,和浏览器客户端完全隔离——浏览器存储的Cookie(如sessionid)无法被服务端组件直接访问,因此fetch请求自然无法携带这些Cookie。而客户端组件在浏览器中执行,能直接读取浏览器的Cookie存储,所以请求可以正常携带Cookie。
另外你提供的getPostByID代码里有个笔误:重复定义了response(写成了respnse),这会导致后续逻辑出错,建议修正。
修复方案
方案1:将请求逻辑移至客户端组件
如果获取Post数据不需要服务端预渲染,直接把请求逻辑放到客户端组件中(比如用useEffect触发,或直接在组件内调用),这样请求在浏览器端执行,就能自动携带Cookie:
// 客户端组件示例 'use client'; import { useEffect, useState } from 'react'; export default function PostClientComponent({ id }: { id: string }) { const [postData, setPostData] = useState(null); useEffect(() => { const fetchPost = async () => { const apiToken = await getCSRFToken(); const response = await fetch(`http://localhost:8000/posts/?id=${id}`, { method: "GET", headers: { "Content-Type": "application/json", "X-CSRFToken": apiToken, }, credentials: "include", }); const data = await response.json(); setPostData(data); }; fetchPost(); }, [id]); return <div>{/* 渲染postData */}</div>; }
方案2:服务端组件手动传递Cookie
如果必须在服务端组件中请求数据,需要从Next.js的请求对象中获取浏览器发送的Cookie,然后手动添加到fetch请求头中:
// 服务端组件中获取Cookie并传递 import { cookies } from 'next/headers'; export async function getPostByID(id: string, cookieString: string) { const apiToken = await getCSRFToken(); const response = await fetch(`http://localhost:8000/posts/?id=${id}`, { method: "GET", headers: { "Content-Type": "application/json", "X-CSRFToken": apiToken, "Cookie": cookieString, // 手动携带Cookie }, credentials: "include", }); const data = await response.json(); return data; } // 服务端组件页面 export default async function PostPage({ params }: { params: { id: string } }) { const cookieStore = cookies(); const cookieString = cookieStore.toString(); // 转换为Cookie字符串格式 const postData = await getPostByID(params.id, cookieString); return <div>{/* 渲染postData */}</div>; }
方案3:确保Django跨域Cookie配置正确
检查并修正Django的settings.py相关配置,确保Cookie能正常跨域传递:
# settings.py INSTALLED_APPS = [ # ... 其他应用 'corsheaders', ] MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', # ... 其他中间件 ] CORS_ALLOW_CREDENTIALS = True CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", # Next.js本地开发地址 ] SESSION_COOKIE_SAMESITE = 'Lax' # 跨域场景下可设为None,注意需要配合SESSION_COOKIE_SECURE=True(生产环境HTTPS) SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_SECURE = False # 本地开发用False,生产环境必须设为True
相关代码参考
获取Post数据的前端代码(原代码)
export async function getPostByID(id: string) { const apiToken = await getCSRFToken(); const response = await fetch( "http://localhost:8000/posts/?id=" + id, { method: "GET", headers: { "Content-Type": "application/json", "X-CSRFToken": apiToken, }, credentials: "include", } ); // 注意:此处笔误,respnse应为response const respnse = await fetch( "http://localhost:8000/posts/comments", { method: "GET", headers: { "Content-Type": "application/json", "X-CSRFToken": apiToken, }, credentials: "include", } ); const data = await response.json(); return data; }
Django PostsView视图代码(原代码)
class PostsView(View): def get(self, request): id = request.GET.get('id') if id is not None: post = get_object_or_404(Post ,id=id) comments = [] for comment in post.comments.all(): comments.append({ 'id': comment.id, 'author': comment.author.username, 'post': comment.post.id, 'content': comment.content, 'created_at': comment.created_at, 'updated_at': comment.updated_at }) try: Like.objects.get(author=request.user.id, post=post.id) liked = True except Like.DoesNotExist: liked = False return JsonResponse({'id': post.id, 'author':{'id': post.author.id, 'email': post.author.email, 'username': post.author.username}, 'title': post.title, 'content': post.content, 'comments': comments, 'created_at': post.created_at, 'updated_at': post.updated_at, 'likes': post.getLikesCount(), 'liked': liked}, status=200) userID = request.GET.get('user') if userID is not None: user = get_object_or_404(User, id=userID) posts = get_list_or_404(Post, author=user) return JsonResponse({'posts': posts}, safe=False, status=200) posts = Post.objects.all() response = {'posts': []} for post in posts: response['posts'].append({'id': post.id, 'author':{'id': post.author.id, 'email': post.author.email, 'username': post.author.username}, 'title': post.title, 'content': post.content, 'created': post.created_at, 'updated': post.updated_at, 'likes': post.getLikesCount()}) return JsonResponse(response, status=200)
登出请求前端代码(原代码)
async function handleLogout() { const apiToken = await getCSRFToken(); const reponse = await fetch("http://localhost:8000/accounts/logout/", { method: "GET", headers: { "Content-Type": "application/json", "X-CSRFToken": apiToken, } as any, credentials: "include", }); const data = await reponse.json(); if (!reponse.ok) { toast.error(data.message, { position: "top-right", autoClose: 5000, hideProgressBar: false, closeOnClick: false, pauseOnHover: false, draggable: false, progress: undefined, theme: "dark", transition: Flip, }); return; } else { toast.success(data.message, { position: "top-right", autoClose: 5000, hideProgressBar: false, closeOnClick: false, pauseOnHover: false, draggable: false, progress: undefined, theme: "dark", transition: Flip, }); context.setUser(undefined); } }
Django LogoutView视图代码(原代码)
class LogoutView(View): def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: return JsonResponse({'message': 'Not logged in!'}, status=401) return super().dispatch(request, *args, **kwargs) def get(self, request): logout(request) return JsonResponse({'message': 'Successfully logged out.'}, status=200)
内容的提问来源于stack exchange,提问作者FrOZEn_FurY

