在Nuxt的SSR(服务端渲染)模式下,服务器端没有localStorage。因此,更通用且安全的方案是使用Cookie来存储Token,因为Cookie在客户端和服务端都能访问,且更适合SSR场景。

cookie-universal-nuxt是Nuxt生态中处理Cookie的优秀工具,它提供了统一的API在客户端和服务端操作Cookie,非常适合实现状态持久化和Token管理。下面是基于该工具的完整方案:

一、安装与配置 cookie-universal-nuxt

1. 安装依赖
npm install cookie-universal-nuxt --save
# 或
yarn add cookie-universal-nuxt
2. 在 nuxt.config.js 中配置
export default {
  modules: [
    'cookie-universal-nuxt' // 注册模块
  ],
  // 可选配置(默认即可)
  cookie: {
    parseJSON: true // 自动解析JSON格式的cookie值
  }
}

配置后,可通过 this.$cookies(组件中)或 context.app.$cookies(服务端上下文,如asyncData、中间件)访问Cookie工具。

二、实现状态持久化与Token管理

1. Vuex中集成Cookie操作

store/user.js 中,通过Cookie实现Token的持久化存储:

// store/user.js
export const state = () => ({
  token: null,
  info: null
})

export const mutations = {
  setUser(state, { token, info }) {
    state.token = token
    state.info = info
  },
  clearUser(state) {
    state.token = null
    state.info = null
  }
}

export const actions = {
  // 初始化:从Cookie加载状态(在Nuxt初始化时调用)
  init({ commit, rootState }) {
    // rootState.$cookies 是cookie-universal-nuxt提供的工具
    const token = rootState.$cookies.get('token')
    const userInfo = rootState.$cookies.get('userInfo')
    
    if (token && userInfo) {
      commit('setUser', { token, info: userInfo })
    }
  },

  // 登录:存储Token到Vuex和Cookie
  async login({ commit, rootState }, { username, password }) {
    try {
      const res = await this.$axios.post('/api/login', { username, password })
      const { token, userInfo } = res.data
      
      // 存储到Vuex
      commit('setUser', { token, info: userInfo })
      
      // 存储到Cookie(设置过期时间,如7天)
      rootState.$cookies.set('token', token, {
        maxAge: 60 * 60 * 24 * 7, // 7天有效期
        path: '/', // 全站可访问
        sameSite: 'lax' // 防止CSRF
      })
      rootState.$cookies.set('userInfo', userInfo, {
        maxAge: 60 * 60 * 24 * 7,
        path: '/'
      })
      
      return true
    } catch (error) {
      console.error('登录失败', error)
      return false
    }
  },

  // 退出登录:清除Vuex和Cookie
  logout({ commit, rootState }) {
    commit('clearUser')
    rootState.$cookies.remove('token')
    rootState.$cookies.remove('userInfo')
  }
}
2. 在Nuxt初始化时加载状态

为了确保页面刷新后状态能从Cookie恢复,需在Nuxt初始化时调用init方法。

创建插件 plugins/init-store.js

export default async ({ store }) => {
  // 初始化时从Cookie加载用户状态
  await store.dispatch('user/init')
}

nuxt.config.js 中注册:

export default {
  plugins: [
    '~/plugins/init-store.js', // 初始化状态
    '~/plugins/axios-interceptor.js' // 后面会讲到的Axios拦截器
  ]
}

三、Token校验与权限控制

1. Axios拦截器(兼容SSR)

修改之前的Axios拦截器,从Vuex获取Token(而非直接读Cookie),确保客户端和服务端一致:

// plugins/axios-interceptor.js
export default function ({ $axios, store, redirect, route }) {
  // 请求拦截器:添加Token
  $axios.interceptors.request.use(config => {
    const token = store.state.user.token
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  })

  // 响应拦截器:处理Token过期
  $axios.interceptors.response.use(
    response => response,
    error => {
      const { response } = error
      if (response && response.status === 401) {
        // 清除状态和Cookie
        store.dispatch('user/logout')
        // 客户端环境下跳转登录页
        if (process.client) {
          redirect(`/login?redirect=${route.path}`)
        }
      }
      return Promise.reject(error)
    }
  )
}
2. 中间件控制路由权限

修改中间件 middleware/auth.js,兼容服务端和客户端:

export default async function ({ store, redirect, route }) {
  // 检查是否已登录
  const isLoggedIn = !!store.state.user.token

  // 未登录且访问需授权页面:跳登录页
  if (!isLoggedIn && route.path !== '/login') {
    return redirect(`/login?redirect=${route.path}`)
  }

  // 已登录访问登录页:跳首页
  if (isLoggedIn && route.path === '/login') {
    return redirect('/')
  }
}

四、登录页实现(同上一方案,无需修改)

<!-- pages/login.vue -->
<template>
  <div>
    <input v-model="username" placeholder="用户名" />
    <input v-model="password" type="password" placeholder="密码" />
    <button @click="handleLogin">登录</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      username: '',
      password: ''
    }
  },
  methods: {
    async handleLogin() {
      const success = await this.$store.dispatch('user/login', {
        username: this.username,
        password: this.password
      })
      if (success) {
        const redirectPath = this.$route.query.redirect || '/'
        this.$router.push(redirectPath)
      }
    }
  }
}
</script>

五、方案优势与注意事项

优势:
  1. SSR兼容:Cookie在客户端和服务端均可访问,避免localStorage在服务端报错的问题。
  2. 安全性:通过httpOnlysameSite等属性可增强Cookie安全性(需后端配合设置)。
  3. 统一APIcookie-universal-nuxt提供了一致的API,无需区分客户端/服务端环境。
  4. 自动过期:可设置Cookie过期时间,实现Token自动失效。
注意事项:
  1. Cookie大小限制:Cookie有4KB大小限制,不宜存储过大的用户信息(建议只存Token,用户信息通过Token在服务端获取)。
  2. 敏感信息保护
    • 重要场景下,后端应将Token设置为httpOnly: true(客户端JS无法读取,防XSS)。
    • 配合secure: true(仅HTTPS传输)和sameSite: 'strict'(防CSRF)增强安全性。
  3. 服务端获取Cookie:在asyncDatafetch中,通过context.app.$cookies访问Cookie,而非this.$cookies

通过cookie-universal-nuxt实现的方案,完美解决了SSR环境下的状态持久化问题,是Nuxt项目中管理Token的推荐方案,兼顾了兼容性和安全性。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐