Nuxt中HttpOnly Cookie页面刷新后丢失,如何安全存储JWT?
问题分析与解决方案
你的核心问题出在对HttpOnly Cookie的理解偏差:HttpOnly Cookie是浏览器专门限制前端JS读取/修改的安全Cookie,完全只能由后端服务来设置和管理。你用Pinia的前端持久化逻辑去写HttpOnly Cookie,本质上是行不通的——前端JS根本没有权限操作HttpOnly Cookie,所以看似“页面刷新后Cookie丢失”,实际上这个Cookie从一开始就没被正确设置过。
下面是完整的修正方案:
一、后端(Django)修改:主动写入HttpOnly Cookie
登录接口的JWT令牌,应该由Django后端直接写入HttpOnly Cookie,而不是返回给前端让前端存储。
修改Django登录视图(示例):
from django.http import JsonResponse from rest_framework_simplejwt.tokens import RefreshToken from django.contrib.auth import authenticate def login_view(request): # 解析前端传入的账号密码 data = request.json() username = data.get('username') password = data.get('password') user = authenticate(username=username, password=password) if user: # 生成JWT令牌 refresh = RefreshToken.for_user(user) access_token = str(refresh.access_token) # 构造响应,将access_token写入HttpOnly Cookie response = JsonResponse({"message": "登录成功"}) response.set_cookie( key="access_token", value=access_token, httponly=True, # 核心:标记为HttpOnly,禁止前端JS访问 secure=True, # 生产环境必须开启,仅在HTTPS下传输 samesite="Strict", max_age=3600, # 和JWT过期时间保持一致(示例1小时) path="/" # 确保全站接口都能携带该Cookie ) return response else: return JsonResponse({"error": "账号密码错误"}, status=401)
同时配置Django跨域(如果前后端域名不同):
安装django-cors-headers后,在settings.py中添加:
INSTALLED_APPS = [ # ...其他依赖 'corsheaders', ] MIDDLEWARE = [ # ...其他中间件 'corsheaders.middleware.CorsMiddleware', ] CORS_ALLOW_CREDENTIALS = True # 允许携带Cookie跨域 CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", # 你的Nuxt前端地址 ]
二、前端(Nuxt+Pinia)修改:依赖后端验证状态
前端不再存储JWT令牌,而是通过后端设置的Cookie维持会话,用户状态通过后端接口验证。
1. 修改Pinia Auth Store
去掉前端持久化逻辑,添加状态验证和登出的action:
import { defineStore } from 'pinia' export const useAuthStore = defineStore({ id: 'auth', state: () => ({ user: { isAuthenticated: false } }), actions: { // 检查用户登录状态:调用后端接口验证Cookie中的JWT async checkAuthStatus() { const response = await useFetch('auth/check-status', { baseURL: 'http://127.0.0.1:8000/', credentials: 'include', // 关键:携带Cookie发送请求 onResponse({ response }) { this.user.isAuthenticated = response._data.is_authenticated }, onResponseError() { this.user.isAuthenticated = false } }) }, // 登出:调用后端接口清除Cookie async logout() { await useFetch('auth/logout', { method: 'POST', baseURL: 'http://127.0.0.1:8000/', credentials: 'include', }) this.user.isAuthenticated = false navigateTo('/login') } } })
2. 修改登录页面逻辑
登录时不需要存储token,只需确保请求携带Cookie,登录成功后直接跳转:
async function submit() { const response = await useFetch('auth/jwt/create', { method: 'POST', baseURL: 'http://127.0.0.1:8000/', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(loginData), credentials: 'include', // 允许接收后端设置的Cookie onResponseError({response}) { console.log('登录失败:' + response.status) }, }) if (response.value.ok) { navigateTo('/') } }
3. 修改全局路由中间件
进入路由前先验证用户状态,避免依赖前端本地缓存:
import { useAuthStore } from "~/stores/auth" export default defineNuxtRouteMiddleware(async (to, from) => { const authStore = useAuthStore() // 首次进入或刷新时,先调用后端接口验证状态 if (!authStore.user.isAuthenticated) { await authStore.checkAuthStatus() } if (to.path !== '/login' && !authStore.user.isAuthenticated) { return navigateTo('/login') } if (to.path === '/login' && authStore.user.isAuthenticated) { return navigateTo('/') } })
三、补充说明
- 后端需要新增
auth/check-status接口:用于验证Cookie中的JWT是否有效,返回{"is_authenticated": true/false} - 后端登出接口需要清除Cookie:
response.delete_cookie("access_token", path="/") - 生产环境必须开启
secure=True,确保Cookie仅在HTTPS下传输,避免被窃听
内容的提问来源于stack exchange,提问作者Shirony
相关产品推荐
相关产品推荐

