如何在SvelteKit与MongoDB项目中实现Cookie身份认证
以下实现基于SvelteKit最新路由规范(+server.ts/+page.server.ts),完整覆盖数据库连接、Hooks校验、接口实现全流程:
前置依赖安装
先安装项目需要的第三方包:
npm install mongoose bcryptjs cookie npm install -D @types/bcryptjs @types/cookie
1. 初始化MongoDB连接与用户模型
创建src/lib/server/db.ts,统一处理数据库连接复用和模型定义:
import mongoose from 'mongoose'; const MONGODB_URI = process.env.MONGODB_URI as string; if (!MONGODB_URI) throw new Error('请在环境变量中配置MONGODB_URI'); // 复用数据库连接,避免每次请求新建连接 let cachedConn = mongoose.connection.readyState; export const connectDB = async () => { if (cachedConn >= 1) return; await mongoose.connect(MONGODB_URI); cachedConn = mongoose.connection.readyState; }; // 用户Schema,存储基础信息和会话ID const userSchema = new mongoose.Schema({ username: { type: String, required: true, unique: true }, passwordHash: { type: String, required: true }, sessionId: { type: String } }); export const User = mongoose.models.User || mongoose.model('User', userSchema);
新增src/app.d.ts声明全局类型,避免TS报错:
declare global { namespace App { interface Locals { user?: { _id: string; username: string; } } } } export {};
2. 配置服务端Hooks实现全局身份校验
创建src/hooks.server.ts,每次请求自动解析Cookie、校验会话有效性:
import type { Handle } from '@sveltejs/kit'; import { connectDB, User } from '$lib/server/db'; export const handle: Handle = async ({ event, resolve }) => { await connectDB(); // 从Cookie中读取会话ID const sessionId = event.cookies.get('session_id'); if (sessionId) { // 校验会话ID对应的用户是否存在 const user = await User.findOne({ sessionId }); if (user) { // 用户信息挂载到locals,后续服务端逻辑可直接读取 event.locals.user = { _id: user._id.toString(), username: user.username }; } else { // 无效会话清除Cookie event.cookies.delete('session_id', { path: '/' }); } } return resolve(event); };
3. 实现认证相关服务端接口
3.1 注册接口
创建src/routes/api/register/+server.ts:
import type { RequestHandler } from './$types'; import { connectDB, User } from '$lib/server/db'; import bcrypt from 'bcryptjs'; import crypto from 'crypto'; export const POST: RequestHandler = async ({ request, cookies }) => { await connectDB(); const { username, password } = await request.json(); // 校验用户是否已存在 const existUser = await User.findOne({ username }); if (existUser) return new Response('用户名已存在', { status: 400 }); // 密码哈希后存储,生成随机会话ID const passwordHash = await bcrypt.hash(password, 10); const sessionId = crypto.randomUUID(); await User.create({ username, passwordHash, sessionId }); // 写入HttpOnly Cookie,禁止前端JS读取,防范XSS cookies.set('session_id', sessionId, { path: '/', httpOnly: true, sameSite: 'strict', secure: process.env.NODE_ENV === 'production', maxAge: 60 * 60 * 24 * 7 // 有效期7天 }); return new Response(JSON.stringify({ success: true }), { status: 200 }); };
3.2 登录接口
创建src/routes/api/login/+server.ts:
import type { RequestHandler } from './$types'; import { connectDB, User } from '$lib/server/db'; import bcrypt from 'bcryptjs'; import crypto from 'crypto'; export const POST: RequestHandler = async ({ request, cookies }) => { await connectDB(); const { username, password } = await request.json(); const user = await User.findOne({ username }); if (!user) return new Response('用户名或密码错误', { status: 400 }); // 比对密码哈希 const passValid = await bcrypt.compare(password, user.passwordHash); if (!passValid) return new Response('用户名或密码错误', { status: 400 }); // 刷新会话ID const sessionId = crypto.randomUUID(); user.sessionId = sessionId; await user.save(); cookies.set('session_id', sessionId, { path: '/', httpOnly: true, sameSite: 'strict', secure: process.env.NODE_ENV === 'production', maxAge: 60 * 60 * 24 * 7 }); return new Response(JSON.stringify({ success: true }), { status: 200 }); };
3.3 登出接口
创建src/routes/api/logout/+server.ts:
import type { RequestHandler } from './$types'; import { connectDB, User } from '$lib/server/db'; export const POST: RequestHandler = async ({ locals, cookies }) => { await connectDB(); if (locals.user) { // 清除数据库中的会话ID await User.findByIdAndUpdate(locals.user._id, { sessionId: null }); } cookies.delete('session_id', { path: '/' }); return new Response(JSON.stringify({ success: true }), { status: 200 }); };
4. 受保护页面校验示例
如需限制登录用户才能访问的页面,在对应路由的+page.server.ts中做校验即可,以仪表盘页面为例:
创建src/routes/dashboard/+page.server.ts:
import type { PageServerLoad } from './$types'; import { redirect } from '@sveltejs/kit'; export const load: PageServerLoad = async ({ locals }) => { // 未登录用户重定向到登录页 if (!locals.user) throw redirect(302, '/login'); return { username: locals.user.username }; };
关键注意点
- 所有涉及数据库操作、密码处理的逻辑必须放在
server目录、+server.ts或+page.server.ts中,避免代码泄露到客户端 - Cookie必须开启
httpOnly: true配置,生产环境需同时开启secure: true和sameSite: 'strict' - 禁止明文存储密码,必须使用bcrypt等不可逆哈希算法加密后入库
- 会话ID需使用UUID等足够长度的随机字符串,避免被暴力破解
内容的提问来源于stack exchange,提问作者Nikolas Blahušiak
相关产品推荐
相关产品推荐

