博客搭建教程(5):登录认证

认证就是"证明你是谁"。你去银行取钱要输密码,登录网站要输入账号密码——这就是认证。 我们用的是 JWT(JSON Web Token) 方案: 1. 用户输入账号密码 → 服务器验证 2. 验证通过 → 服务器签发一个"令牌"(JWT) 3. 令牌存在浏览器的 Cookie 里 4. 之后每次请求,浏览器自动带这个 Cookie,服务器就知道你是谁了 Cookie 是浏览器存储的一小段数据。每次...

博客搭建教程(5):登录认证与文章管理后台

什么是认证(Authentication)?

认证就是"证明你是谁"。你去银行取钱要输密码,登录网站要输入账号密码——这就是认证。

我们用的是 JWT(JSON Web Token) 方案:

  1. 用户输入账号密码 → 服务器验证
  2. 验证通过 → 服务器签发一个"令牌"(JWT)
  3. 令牌存在浏览器的 Cookie 里
  4. 之后每次请求,浏览器自动带这个 Cookie,服务器就知道你是谁了

Cookie 是浏览器存储的一小段数据。每次访问网站,浏览器会自动把该网站的 Cookie 发给服务器。httpOnly 的 Cookie 无法被 JavaScript 读取,更安全。

第一步:安装认证库

npm install jose bcryptjs
npm install -D @types/bcryptjs
  • jose:创建和验证 JWT 令牌
  • bcryptjs:加密密码(不能存明文密码!)

第二步:用户模型

在 schema.prisma 加:

model User {
  id        String   @id @default(cuid())
  username  String   @unique
  password  String
  apiKey    String?  @unique
  createdAt DateTime @default(now())
}

绝对不能存明文密码! 存的是 bcrypt 哈希后的结果。即使数据库泄露,攻击者也拿不到原始密码。

npx prisma db push

第三步:认证工具函数

src/lib/auth.ts

import bcrypt from "bcryptjs";
import { SignJWT, jwtVerify } from "jose";

const SECRET = new TextEncoder().encode(process.env.JWT_SECRET!);

// 加密密码
export async function hashPassword(pw: string) {
  return bcrypt.hash(pw, 10);
}

// 验证密码
export async function verifyPassword(pw: string, hash: string) {
  return bcrypt.compare(pw, hash);
}

// 签发 JWT
export async function createToken(userId: string, username: string) {
  return new SignJWT({ userId, username })
    .setProtectedHeader({ alg: "HS256" })
    .setExpirationTime("7d")
    .sign(SECRET);
}

// 验证 JWT
export async function verifyToken(token: string) {
  try {
    const { payload } = await jwtVerify(token, SECRET);
    return payload as { userId: string; username: string };
  } catch {
    return null;
  }
}

第四步:登录 API

src/app/api/auth/login/route.ts

export async function POST(request: NextRequest) {
  const { username, password } = await request.json();

  const user = await prisma.user.findUnique({ where: { username } });
  if (!user) return NextResponse.json({ error: "用户名或密码错误" }, { status: 401 });

  const valid = await verifyPassword(password, user.password);
  if (!valid) return NextResponse.json({ error: "用户名或密码错误" }, { status: 401 });

  const token = await createToken(user.id, user.username);

  // 设置 httpOnly cookie
  const response = NextResponse.json({ success: true });
  response.cookies.set("session", token, {
    httpOnly: true,
    secure: true,
    sameSite: "lax",
    path: "/",
    maxAge: 60 * 60 * 24 * 7,
  });
  return response;
}

第五步:保护管理后台

src/middleware.ts

export default async function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith("/admin") &&
      !request.nextUrl.pathname.startsWith("/admin/login")) {
    const token = request.cookies.get("session")?.value;
    if (!token) {
      return NextResponse.redirect(new URL("/admin/login", request.url));
    }
    try {
      await jwtVerify(token, SECRET);
      return NextResponse.next();
    } catch {
      return NextResponse.redirect(new URL("/admin/login", request.url));
    }
  }
}

所有 /admin/* 的请求(除了登录页)都会被检查,没登录就跳转登录页。

第六步:文章管理页面

管理后台页面结构:

src/app/admin/
├── layout.tsx           # 侧边栏布局
├── page.tsx             # 文章列表(表格)
├── login/page.tsx       # 登录表单
└── articles/
    ├── new/page.tsx     # 新建文章
    └── [id]/page.tsx    # 编辑文章

新建/编辑文章用 Markdown 编辑器(@uiw/react-md-editor),实时预览。

小结

这篇我们:

  1. 理解了认证和 JWT 的原理
  2. 用 bcrypt 安全存储密码
  3. 实现了登录/登出
  4. 用 middleware 保护管理后台路由
  5. 搭建了文章管理界面

下一篇介绍怎么做二次元主题、樱花特效和部署上线。


💬 评论

加载中...