You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SvelteKit与MongoDB项目中实现Cookie身份认证

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 13:57:03