会话与认证
介绍
在本食谱中,我们将使用 Nuxt Auth Utils 设置一个全栈的 Nuxt 应用的认证,它提供了管理客户端和服务器端会话数据的便利工具。
该模块使用安全且密封的 cookie 来存储会话数据,因此您无需设置数据库来存储会话数据。
安装 nuxt-auth-utils
使用 nuxi
CLI 安装 nuxt-auth-utils
模块。
npx nuxi@latest module add auth-utils
nuxt-auth-utils
安装为依赖项,并将其推入我们 nuxt.config.ts
的 modules
部分Cookie 加密密钥
由于 nuxt-auth-utils
使用密封的 cookie 来存储会话数据,会话 cookie 是使用 NUXT_SESSION_PASSWORD
环境变量中的密钥进行加密的。
.env
文件中。NUXT_SESSION_PASSWORD=一个至少 32 个字符的随机密码
登录 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 应用示例。