会话与认证
介绍
在本教程中,我们将使用 Nuxt Auth Utils 在一个全栈 Nuxt 应用中设置认证。该工具库为管理客户端和服务器端的会话数据提供了便捷的工具。
该模块使用安全且密封的 Cookie 来存储会话数据,因此你无需设置数据库来存储会话信息。
安装 nuxt-auth-utils
使用 nuxt
CLI 安装 nuxt-auth-utils
模块。
npx nuxt module add auth-utils
nuxt-auth-utils
作为依赖,并将其添加到 nuxt.config.ts
的 modules
部分。Cookie 加密密钥
由于 nuxt-auth-utils
使用密封的 Cookie 存储会话数据,会话 Cookie 会通过环境变量 NUXT_SESSION_PASSWORD
中的密钥进行加密。
.env
文件中。NUXT_SESSION_PASSWORD=a-random-password-with-at-least-32-characters
登录 API 路由
本教程中,我们将创建一个简单的 API 路由,根据静态数据实现用户登录功能。
创建一个 /api/login
的 API 路由,接受包含邮箱和密码的 POST 请求体。
import { z } from 'zod'
const bodySchema = z.object({
email: z.string().email(),
password: z.string().min(8)
})
export default defineEventHandler(async (event) => {
const { email, password } = await readValidatedBody(event, bodySchema.parse)
if (email === 'admin@admin.com' && password === 'iamtheadmin') {
// 设置用户会话到 Cookie
// 该服务器工具由 auth-utils 模块自动导入
await setUserSession(event, {
user: {
name: 'John Doe'
}
})
return {}
}
throw createError({
statusCode: 401,
message: '认证失败'
})
})
zod
依赖(npm i zod
)。登录页面
该模块暴露了一个 Vue 组合函数,用于判断用户是否已认证:
<script setup>
const { loggedIn, session, user, clear, fetch } = useUserSession()
</script>
我们来创建一个登录页面,包含一个表单用于提交登录数据到 /api/login
路由。
<script setup lang="ts">
const { loggedIn, user, fetch: refreshSession } = useUserSession()
const credentials = reactive({
email: '',
password: '',
})
async function login() {
$fetch('/api/login', {
method: 'POST',
body: credentials
})
.then(async () => {
// 客户端刷新会话并重定向到首页
await refreshSession()
await navigateTo('/')
})
.catch(() => alert('认证失败'))
}
</script>
<template>
<form @submit.prevent="login">
<input v-model="credentials.email" type="email" placeholder="邮箱" />
<input v-model="credentials.password" type="password" placeholder="密码" />
<button type="submit">登录</button>
</form>
</template>
保护 API 路由
保护服务器路由对于确保数据安全至关重要。客户端中间件对用户体验有帮助,但如果没有服务器端保护,你的数据仍然可能被访问。对包含敏感数据的路由,未登录用户应返回 401 错误。
auth-utils
模块提供了 requireUserSession
工具函数,帮助确认用户是否已登录且会话有效。
我们创建一个 /api/user/stats
路由示例,仅允许认证用户访问:
export default defineEventHandler(async (event) => {
// 确认用户已登录
// 若请求未来自有效用户会话,将抛出 401 错误
const { user } = await requireUserSession(event)
// TODO: 根据用户获取一些统计数据
return {}
});
保护应用路由
服务器端路由保护确保数据安全,但如果不做额外操作,未认证用户访问 /users
页时仍可能看到异常内容。我们需要创建一个客户端中间件,保护客户端路由并重定向未认证用户到登录页。
nuxt-auth-utils
提供了便捷的 useUserSession
组合函数,我们会用它检测用户登录状态,并在未登录时重定向。
在 /middleware
目录下创建中间件。与服务器不同,客户端中间件不会自动应用到所有路由,需手动指定应用范围。
export default defineNuxtRouteMiddleware(() => {
const { loggedIn } = useUserSession()
// 如果用户未认证,则重定向到登录页
if (!loggedIn.value) {
return navigateTo('/login')
}
})
首页
现在我们有了客户端中间件保护路由,可以在首页显示认证用户信息。若用户未认证,将被重定向到登录页。
我们通过 definePageMeta
为需要保护的路由应用中间件。
<script setup lang="ts">
definePageMeta({
middleware: ['authenticated'],
})
const { user, clear: clearSession } = useUserSession()
async function logout() {
await clearSession()
await navigateTo('/login')
}
</script>
<template>
<div>
<h1>欢迎 {{ user.name }}</h1>
<button @click="logout">登出</button>
</div>
</template>
我们还添加了一个登出按钮,用以清除会话并重定向用户到登录页。
总结
我们已成功在 Nuxt 应用中设置了基础的用户认证和会话管理。同时保护了服务器端和客户端的敏感路由,确保只有认证用户可以访问。
后续可考虑:
- 使用20+ 支持的 OAuth 提供商实现认证
- 添加数据库存储用户,参考 Nitro SQL 数据库 或 NuxtHub SQL 数据库
- 允许用户通过邮件和密码注册,结合密码加密
- 支持 WebAuthn / Passkeys
请查看开源的 atidone 仓库,这里有一个包含 OAuth 认证、数据库和 CRUD 操作的 Nuxt 应用完整示例。