博客搭建教程(5):登录认证
认证就是"证明你是谁"。你去银行取钱要输密码,登录网站要输入账号密码——这就是认证。 我们用的是 JWT(JSON Web Token) 方案: 1. 用户输入账号密码 → 服务器验证 2. 验证通过 → 服务器签发一个"令牌"(JWT) 3. 令牌存在浏览器的 Cookie 里 4. 之后每次请求,浏览器自动带这个 Cookie,服务器就知道你是谁了 Cookie 是浏览器存储的一小段数据。每次...
博客搭建教程(5):登录认证与文章管理后台
什么是认证(Authentication)?
认证就是"证明你是谁"。你去银行取钱要输密码,登录网站要输入账号密码——这就是认证。
我们用的是 JWT(JSON Web Token) 方案:
- 用户输入账号密码 → 服务器验证
- 验证通过 → 服务器签发一个"令牌"(JWT)
- 令牌存在浏览器的 Cookie 里
- 之后每次请求,浏览器自动带这个 Cookie,服务器就知道你是谁了
什么是 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),实时预览。
小结
这篇我们:
- 理解了认证和 JWT 的原理
- 用 bcrypt 安全存储密码
- 实现了登录/登出
- 用 middleware 保护管理后台路由
- 搭建了文章管理界面
下一篇介绍怎么做二次元主题、樱花特效和部署上线。