Remix Session管理 #

一、Session概述 #

Session 用于在请求之间存储用户数据,常用于用户认证、购物车等场景。

二、创建Session存储 #

tsx
import { createCookieSessionStorage } from "@remix-run/node";

const sessionSecret = process.env.SESSION_SECRET;
if (!sessionSecret) {
  throw new Error("SESSION_SECRET must be set");
}

export const sessionStorage = createCookieSessionStorage({
  cookie: {
    name: "__session",
    httpOnly: true,
    maxAge: 60 * 60 * 24 * 7, // 7天
    path: "/",
    sameSite: "lax",
    secrets: [sessionSecret],
    secure: process.env.NODE_ENV === "production",
  },
});

export const { getSession, commitSession, destroySession } = sessionStorage;

2.2 文件系统Session #

tsx
import { createFileSessionStorage } from "@remix-run/node";

export const sessionStorage = createFileSessionStorage({
  dir: "./sessions",
  cookie: {
    name: "__session",
    secrets: [process.env.SESSION_SECRET],
  },
});

2.3 数据库Session #

tsx
import { createSessionStorage } from "@remix-run/node";

export const sessionStorage = createSessionStorage({
  cookie: {
    name: "__session",
    secrets: [process.env.SESSION_SECRET],
  },
  async createData(data, expires) {
    const id = uuid();
    await db.session.create({
      data: { id, data, expiresAt: expires },
    });
    return id;
  },
  async readData(id) {
    const session = await db.session.findUnique({ where: { id } });
    return session?.data;
  },
  async updateData(id, data, expires) {
    await db.session.update({
      where: { id },
      data: { data, expiresAt: expires },
    });
  },
  async deleteData(id) {
    await db.session.delete({ where: { id } });
  },
});

三、使用Session #

3.1 获取Session #

tsx
import type { LoaderFunctionArgs } from "@remix-run/node";
import { getSession } from "~/session.server";

export async function loader({ request }: LoaderFunctionArgs) {
  const session = await getSession(request.headers.get("Cookie"));
  const userId = session.get("userId");
  
  return json({ userId });
}

3.2 设置Session数据 #

tsx
import type { ActionFunctionArgs } from "@remix-run/node";
import { getSession, commitSession } from "~/session.server";

export async function action({ request }: ActionFunctionArgs) {
  const session = await getSession(request.headers.get("Cookie"));
  
  session.set("userId", user.id);
  session.set("userName", user.name);
  
  return redirect("/dashboard", {
    headers: {
      "Set-Cookie": await commitSession(session),
    },
  });
}

3.3 销毁Session #

tsx
export async function action({ request }: ActionFunctionArgs) {
  const session = await getSession(request.headers.get("Cookie"));
  
  return redirect("/login", {
    headers: {
      "Set-Cookie": await destroySession(session),
    },
  });
}

四、Flash消息 #

4.1 设置Flash消息 #

tsx
export async function action({ request }: ActionFunctionArgs) {
  const session = await getSession(request.headers.get("Cookie"));
  
  session.flash("success", "登录成功!");
  
  return redirect("/dashboard", {
    headers: {
      "Set-Cookie": await commitSession(session),
    },
  });
}

4.2 读取Flash消息 #

tsx
export async function loader({ request }: LoaderFunctionArgs) {
  const session = await getSession(request.headers.get("Cookie"));
  const success = session.get("success");
  
  return json(
    { success },
    {
      headers: {
        "Set-Cookie": await commitSession(session),
      },
    }
  );
}

五、用户认证 #

5.1 登录 #

tsx
import { json, redirect } from "@remix-run/node";
import bcrypt from "bcryptjs";
import { getSession, commitSession } from "~/session.server";

export async function action({ request }: ActionFunctionArgs) {
  const formData = await request.formData();
  const email = formData.get("email");
  const password = formData.get("password");
  
  const user = await getUserByEmail(email);
  
  if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
    return json({ error: "邮箱或密码错误" }, { status: 400 });
  }
  
  const session = await getSession(request.headers.get("Cookie"));
  session.set("userId", user.id);
  
  return redirect("/dashboard", {
    headers: {
      "Set-Cookie": await commitSession(session),
    },
  });
}

5.2 登出 #

tsx
export async function action({ request }: ActionFunctionArgs) {
  const session = await getSession(request.headers.get("Cookie"));
  
  return redirect("/login", {
    headers: {
      "Set-Cookie": await destroySession(session),
    },
  });
}

5.3 获取当前用户 #

tsx
export async function requireUserId(request: Request) {
  const session = await getSession(request.headers.get("Cookie"));
  const userId = session.get("userId");
  
  if (!userId) {
    throw redirect("/login");
  }
  
  return userId;
}

export async function requireUser(request: Request) {
  const userId = await requireUserId(request);
  const user = await getUser(userId);
  
  if (!user) {
    throw redirect("/login");
  }
  
  return user;
}

六、Session工具函数 #

6.1 封装Session操作 #

tsx
export async function createUserSession(
  userId: string,
  redirectTo: string
) {
  const session = await getSession();
  session.set("userId", userId);
  
  return redirect(redirectTo, {
    headers: {
      "Set-Cookie": await commitSession(session),
    },
  });
}

export async function getUser(request: Request) {
  const session = await getSession(request.headers.get("Cookie"));
  const userId = session.get("userId");
  
  if (!userId) return null;
  
  return await db.user.findUnique({ where: { id: userId } });
}

七、安全最佳实践 #

7.1 Cookie安全配置 #

tsx
export const sessionStorage = createCookieSessionStorage({
  cookie: {
    name: "__session",
    httpOnly: true,       // 防止XSS
    secure: true,         // 仅HTTPS
    sameSite: "lax",      // 防止CSRF
    secrets: [sessionSecret],
    maxAge: 60 * 60 * 24 * 7,
    path: "/",
  },
});

7.2 Session过期 #

tsx
export async function loader({ request }: LoaderFunctionArgs) {
  const session = await getSession(request.headers.get("Cookie"));
  const expiresAt = session.get("expiresAt");
  
  if (expiresAt && new Date(expiresAt) < new Date()) {
    throw redirect("/login", {
      headers: {
        "Set-Cookie": await destroySession(session),
      },
    });
  }
  
  // ...
}

八、总结 #

本章我们学习了:

  1. Session存储:Cookie、文件、数据库存储
  2. Session操作:获取、设置、销毁
  3. Flash消息:一次性消息
  4. 用户认证:登录、登出、获取用户
  5. 安全实践:Cookie安全配置

核心要点:

  • 选择合适的Session存储方式
  • 正确设置Cookie安全属性
  • 使用Flash消息传递提示
最后更新:2026-03-28