NextAuth.js 超详细使用指南与原理
目录
- [NextAuth.js 简介](#NextAuth.js 简介 "#nextauthjs-%E7%AE%80%E4%BB%8B")
- 核心架构原理
- 工作流程详解
- 配置详解
- 安全机制
- 高级特性
- [Node.js 原生实现](#Node.js 原生实现 "#nodejs-%E5%8E%9F%E7%94%9F%E5%AE%9E%E7%8E%B0")
NextAuth.js 简介
什么是 NextAuth.js
NextAuth.js 是一个专为 Next.js 应用设计的开源认证解决方案,支持多种认证方式:
- Credentials认证(用户名/密码)
- OAuth 2.0(Google、GitHub、Facebook等)
- Magic Links(无密码邮箱登录)
- 企业级SSO(Azure AD、Auth0等)
核心优势
- 零配置 - 默认配置即可使用
- 安全性 - 内置CSRF、XSS防护
- 灵活性 - 支持自定义认证逻辑
- 多框架 - 支持Next.js、Express等
- TypeScript - 完整的类型支持
核心架构原理
1. 整体架构图
scss
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Client │ │ Next.js API │ │ Database │
│ (Browser) │───▶│ Routes │───▶│ (Sessions) │
│ │ │ /api/auth/* │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ ┌─────────────────┐ │
└─────────────▶│ JWT Tokens │◀─────────────┘
│ & Cookies │
└─────────────────┘
2. 核心组件
2.1 Session管理
typescript
// 核心接口定义
interface Session {
user: {
id: string
email: string
name?: string
image?: string
}
expires: string
accessToken?: string
}
interface JWT {
token: {
sub: string // 用户ID
email: string // 邮箱
name?: string // 姓名
picture?: string // 头像
iat: number // 签发时间
exp: number // 过期时间
}
}
2.2 Cookie安全机制
typescript
// 默认Cookie配置
const defaultCookies = {
sessionToken: {
name: `next-auth.session-token`,
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: true // HTTPS only
}
},
callbackUrl: {
name: `next-auth.callback-url`,
options: {
sameSite: 'lax',
path: '/',
secure: true
}
},
csrfToken: {
name: `next-auth.csrf-token`,
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: true
}
}
}
工作流程详解
1. 登录流程(Credentials)
1.1 时序图
sequenceDiagram
participant U as 用户
participant C as 客户端
participant A as /api/auth/signin
participant P as Provider
participant D as 数据库
participant S as SessionStore
U->>C: 输入邮箱密码
C->>A: POST /api/auth/callback/credentials
A->>P: 调用authorize函数
P->>D: 验证用户凭据
D-->>P: 返回用户信息
P-->>A: 返回用户对象
A->>S: 创建会话
S-->>A: 返回session token
A-->>C: 设置cookie
C-->>U: 登录成功
1.2 详细步骤
步骤1:用户提交表单
typescript
// 客户端代码
import { signIn } from 'next-auth/react'
const handleSubmit = async (e: FormEvent) => {
e.preventDefault()
const result = await signIn('credentials', {
email: 'user@example.com',
password: 'password123',
redirect: false, // 手动处理重定向
})
if (result?.error) {
// 处理错误
console.error('登录失败:', result.error)
} else {
// 登录成功
router.push('/dashboard')
}
}
步骤2:服务端验证
typescript
// /api/auth/[...nextauth].ts
import NextAuth from 'next-auth'
import CredentialsProvider from 'next-auth/providers/credentials'
export default NextAuth({
providers: [
CredentialsProvider({
name: 'credentials',
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" }
},
async authorize(credentials, req) {
// 1. 验证输入
if (!credentials?.email || !credentials?.password) {
throw new Error('Missing credentials')
}
// 2. 查询用户
const user = await User.findOne({ email: credentials.email })
if (!user) {
throw new Error('User not found')
}
// 3. 验证密码
const isValid = await bcrypt.compare(credentials.password, user.password)
if (!isValid) {
throw new Error('Invalid password')
}
// 4. 返回用户对象
return {
id: user._id.toString(),
email: user.email,
name: user.name,
image: user.avatar
}
}
})
]
})
2. Session验证流程
2.1 JWT验证
typescript
// JWT验证过程
async function verifyJWT(token: string) {
try {
// 1. 解码JWT
const decoded = jwt.verify(token, process.env.NEXTAUTH_SECRET!)
// 2. 验证过期时间
if (decoded.exp && Date.now() >= decoded.exp * 1000) {
throw new Error('Token expired')
}
// 3. 验证签名
const signature = crypto
.createHmac('sha256', process.env.NEXTAUTH_SECRET!)
.update(JSON.stringify(decoded))
.digest('hex')
return decoded
} catch (error) {
throw new Error('Invalid token')
}
}
配置详解
1. 基础配置
typescript
// next-auth.config.ts
import { NextAuthOptions } from 'next-auth'
export const authOptions: NextAuthOptions = {
// 1. 认证提供商
providers: [
CredentialsProvider({
name: 'credentials',
credentials: {
email: { label: "邮箱", type: "email" },
password: { label: "密码", type: "password" }
},
async authorize(credentials) {
// 认证逻辑
return user || null
}
}),
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!
})
],
// 2. 会话配置
session: {
strategy: 'jwt', // 或 'database'
maxAge: 30 * 24 * 60 * 60, // 30天
updateAge: 24 * 60 * 60, // 24小时更新一次
},
// 3. JWT配置
jwt: {
maxAge: 30 * 24 * 60 * 60, // 30天
},
// 4. 页面路由
pages: {
signIn: '/auth/signin',
signOut: '/auth/signout',
error: '/auth/error',
verifyRequest: '/auth/verify-request',
newUser: '/auth/new-user'
},
// 5. 回调函数
callbacks: {
async jwt({ token, user, account }) {
// JWT创建/更新时调用
if (user) {
token.id = user.id
token.email = user.email
}
return token
},
async session({ session, token }) {
// 会话创建时调用
if (token) {
session.user.id = token.id
session.user.email = token.email
}
return session
},
async signIn({ user, account, profile }) {
// 登录前验证
return true // 允许登录
},
async redirect({ url, baseUrl }) {
// 重定向控制
return url.startsWith(baseUrl) ? url : baseUrl
}
},
// 6. 事件监听
events: {
async signIn({ user, account, profile }) {
// 登录成功事件
console.log('User signed in:', user.email)
},
async signOut({ token }) {
// 登出事件
console.log('User signed out')
}
}
}
2. 数据库适配器
typescript
// MongoDB适配器
import { MongoDBAdapter } from '@next-auth/mongodb-adapter'
import clientPromise from '@/lib/mongodb'
export const authOptions: NextAuthOptions = {
adapter: MongoDBAdapter(clientPromise),
providers: [
// ... providers
],
session: {
strategy: 'database' // 使用数据库存储会话
}
}
安全机制
1. CSRF保护
typescript
// CSRF Token生成
function generateCSRFToken() {
return crypto.randomBytes(32).toString('hex')
}
// CSRF验证中间件
async function csrfProtection(req: NextApiRequest, res: NextApiResponse) {
const csrfToken = req.cookies['next-auth.csrf-token']
const csrfHeader = req.headers['x-csrf-token']
if (csrfToken !== csrfHeader) {
return res.status(403).json({ error: 'CSRF token mismatch' })
}
}
2. 密码安全
typescript
// 密码强度验证
const passwordSchema = z.object({
password: z.string()
.min(8, '密码至少8位')
.regex(/[A-Z]/, '至少一个大写字母')
.regex(/[a-z]/, '至少一个小写字母')
.regex(/[0-9]/, '至少一个数字')
.regex(/[^A-Za-z0-9]/, '至少一个特殊字符')
})
// 密码哈希
async function hashPassword(password: string): Promise<string> {
const saltRounds = 12
return await bcrypt.hash(password, saltRounds)
}
// 密码验证
async function verifyPassword(
plainPassword: string,
hashedPassword: string
): Promise<boolean> {
return await bcrypt.compare(plainPassword, hashedPassword)
}
3. 会话安全
typescript
// 会话配置
const sessionConfig = {
cookie: {
secure: process.env.NODE_ENV === 'production', // HTTPS only
httpOnly: true, // 防止XSS
sameSite: 'lax', // CSRF保护
maxAge: 30 * 24 * 60 * 60, // 30天
path: '/',
},
rolling: true, // 每次请求更新过期时间
genid: () => crypto.randomUUID(), // 随机会话ID
}
高级特性
1. 角色权限管理
typescript
// 角色定义
enum UserRole {
ADMIN = 'admin',
USER = 'user',
MODERATOR = 'moderator'
}
// 权限检查
function hasPermission(userRole: UserRole, requiredRole: UserRole): boolean {
const roleHierarchy = {
[UserRole.USER]: 1,
[UserRole.MODERATOR]: 2,
[UserRole.ADMIN]: 3
}
return roleHierarchy[userRole] >= roleHierarchy[requiredRole]
}
// JWT扩展
async function jwt({ token, user }) {
if (user) {
token.role = user.role
token.permissions = user.permissions
}
return token
}
async function session({ session, token }) {
session.user.role = token.role
session.user.permissions = token.permissions
return session
}
2. 多因素认证
typescript
// 2FA配置
async function enableTwoFactor(userId: string, secret: string) {
const qrCode = speakeasy.totp({
secret: secret,
encoding: 'base32',
label: `MyApp (${user.email})`,
issuer: 'MyApp'
})
await User.updateOne(
{ _id: userId },
{ twoFactorSecret: secret, twoFactorEnabled: true }
)
return qrCode
}
// 2FA验证
async function verifyTwoFactorToken(userId: string, token: string) {
const user = await User.findById(userId)
return speakeasy.totp.verify({
secret: user.twoFactorSecret,
encoding: 'base32',
token: token,
window: 2 // 允许30秒时间差
})
}
3. 会话管理
typescript
// 获取所有活跃会话
async function getActiveSessions(userId: string) {
return await Session.find({ userId })
.sort({ createdAt: -1 })
.limit(10)
}
// 撤销会话
async function revokeSession(sessionId: string) {
await Session.deleteOne({ _id: sessionId })
}
// 撤销所有会话
async function revokeAllSessions(userId: string) {
await Session.deleteMany({ userId })
}
Node.js 原生实现
下面我们用纯Node.js实现一个简化版的NextAuth认证系统:
1. 项目结构
bash
simple-auth/
├── src/
│ ├── auth/
│ │ ├── index.js # 主认证模块
│ │ ├── providers.js # 认证提供商
│ │ ├── session.js # 会话管理
│ │ ├── crypto.js # 加密工具
│ │ └── middleware.js # 中间件
│ ├── routes/
│ │ ├── auth.js # 认证路由
│ │ └── protected.js # 受保护路由
│ ├── models/
│ │ └── user.js # 用户模型
│ └── server.js # 主服务器
├── .env
└── package.json
2. 核心实现
2.1 加密工具 (src/auth/crypto.js)
javascript
const crypto = require('crypto');
const bcrypt = require('bcrypt');
class CryptoUtils {
// 生成随机字符串
static generateRandomString(length = 32) {
return crypto.randomBytes(length).toString('hex');
}
// 哈希密码
static async hashPassword(password) {
const saltRounds = 12;
return await bcrypt.hash(password, saltRounds);
}
// 验证密码
static async verifyPassword(plainPassword, hashedPassword) {
return await bcrypt.compare(plainPassword, hashedPassword);
}
// 生成JWT
static generateJWT(payload, secret, expiresIn = '7d') {
const header = {
alg: 'HS256',
typ: 'JWT'
};
const now = Math.floor(Date.now() / 1000);
const exp = now + (expiresIn === '7d' ? 7 * 24 * 60 * 60 : expiresIn);
const jwtPayload = {
...payload,
iat: now,
exp: exp
};
const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64url');
const encodedPayload = Buffer.from(JSON.stringify(jwtPayload)).toString('base64url');
const signature = crypto
.createHmac('sha256', secret)
.update(`${encodedHeader}.${encodedPayload}`)
.digest('base64url');
return `${encodedHeader}.${encodedPayload}.${signature}`;
}
// 验证JWT
static verifyJWT(token, secret) {
try {
const [header, payload, signature] = token.split('.');
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(`${header}.${payload}`)
.digest('base64url');
if (signature !== expectedSignature) {
throw new Error('Invalid signature');
}
const decoded = JSON.parse(Buffer.from(payload, 'base64url').toString());
if (decoded.exp && Date.now() >= decoded.exp * 1000) {
throw new Error('Token expired');
}
return decoded;
} catch (error) {
throw new Error('Invalid token');
}
}
// 生成CSRF令牌
static generateCSRFToken() {
return this.generateRandomString(32);
}
// 生成会话ID
static generateSessionId() {
return `sess_${this.generateRandomString(24)}`;
}
}
module.exports = CryptoUtils;
2.2 会话管理 (src/auth/session.js)
javascript
const CryptoUtils = require('./crypto');
class SessionManager {
constructor(options = {}) {
this.sessions = new Map(); // 内存存储(生产环境用Redis)
this.secret = options.secret || process.env.AUTH_SECRET || 'fallback-secret';
this.maxAge = options.maxAge || 7 * 24 * 60 * 60 * 1000; // 7天
}
// 创建会话
async createSession(userId, userData) {
const sessionId = CryptoUtils.generateSessionId();
const token = CryptoUtils.generateJWT(
{
sub: userId,
email: userData.email,
name: userData.name
},
this.secret,
Math.floor(this.maxAge / 1000)
);
const session = {
id: sessionId,
userId,
token,
userData,
createdAt: new Date(),
expiresAt: new Date(Date.now() + this.maxAge),
lastAccessed: new Date()
};
this.sessions.set(sessionId, session);
return {
sessionId,
token,
expires: session.expiresAt
};
}
// 验证会话
async validateSession(sessionId) {
const session = this.sessions.get(sessionId);
if (!session) {
return null;
}
if (session.expiresAt < new Date()) {
this.sessions.delete(sessionId);
return null;
}
// 更新最后访问时间
session.lastAccessed = new Date();
try {
const decoded = CryptoUtils.verifyJWT(session.token, this.secret);
return {
sessionId,
userId: decoded.sub,
email: decoded.email,
name: decoded.name,
expires: session.expiresAt
};
} catch (error) {
this.sessions.delete(sessionId);
return null;
}
}
// 销毁会话
async destroySession(sessionId) {
return this.sessions.delete(sessionId);
}
// 获取用户所有会话
async getUserSessions(userId) {
const userSessions = [];
for (const [id, session] of this.sessions) {
if (session.userId === userId) {
userSessions.push({
id,
createdAt: session.createdAt,
expiresAt: session.expiresAt,
lastAccessed: session.lastAccessed
});
}
}
return userSessions;
}
// 清理过期会话
async cleanupExpiredSessions() {
const now = new Date();
let cleanedCount = 0;
for (const [id, session] of this.sessions) {
if (session.expiresAt < now) {
this.sessions.delete(id);
cleanedCount++;
}
}
return cleanedCount;
}
// 获取会话统计
async getSessionStats() {
const now = new Date();
let active = 0;
let expired = 0;
for (const session of this.sessions.values()) {
if (session.expiresAt > now) {
active++;
} else {
expired++;
}
}
return {
total: this.sessions.size,
active,
expired
};
}
}
module.exports = SessionManager;
2.3 用户模型 (src/models/user.js)
javascript
const fs = require('fs');
const path = require('path');
const CryptoUtils = require('../auth/crypto');
class UserModel {
constructor() {
this.dataFile = path.join(__dirname, '../data/users.json');
this.ensureDataFile();
this.users = this.loadUsers();
}
ensureDataFile() {
const dir = path.dirname(this.dataFile);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
if (!fs.existsSync(this.dataFile)) {
fs.writeFileSync(this.dataFile, JSON.stringify([]));
}
}
loadUsers() {
try {
const data = fs.readFileSync(this.dataFile, 'utf8');
return JSON.parse(data);
} catch (error) {
return [];
}
}
saveUsers() {
fs.writeFileSync(this.dataFile, JSON.stringify(this.users, null, 2));
}
async createUser(userData) {
const existingUser = this.users.find(u => u.email === userData.email);
if (existingUser) {
throw new Error('Email already exists');
}
const user = {
id: CryptoUtils.generateRandomString(16),
email: userData.email,
name: userData.name,
password: await CryptoUtils.hashPassword(userData.password),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
this.users.push(user);
this.saveUsers();
// 返回不包含密码的用户对象
const { password, ...userWithoutPassword } = user;
return userWithoutPassword;
}
async findByEmail(email) {
const user = this.users.find(u => u.email === email);
if (!user) return null;
const { password, ...userWithoutPassword } = user;
return {
...userWithoutPassword,
password // 仅在需要时包含密码
};
}
async findById(id) {
const user = this.users.find(u => u.id === id);
if (!user) return null;
const { password, ...userWithoutPassword } = user;
return userWithoutPassword;
}
async validateCredentials(email, password) {
const user = await this.findByEmail(email);
if (!user || !user.password) {
return null;
}
const isValid = await CryptoUtils.verifyPassword(password, user.password);
if (!isValid) {
return null;
}
const { password: _, ...userWithoutPassword } = user;
return userWithoutPassword;
}
async updateUser(id, updates) {
const userIndex = this.users.findIndex(u => u.id === id);
if (userIndex === -1) {
throw new Error('User not found');
}
if (updates.password) {
updates.password = await CryptoUtils.hashPassword(updates.password);
}
this.users[userIndex] = {
...this.users[userIndex],
...updates,
updatedAt: new Date().toISOString()
};
this.saveUsers();
const { password, ...userWithoutPassword } = this.users[userIndex];
return userWithoutPassword;
}
getAllUsers() {
return this.users.map(({ password, ...user }) => user);
}
}
module.exports = UserModel;
2.4 认证中间件 (src/auth/middleware.js)
javascript
const SessionManager = require('./session');
class AuthMiddleware {
constructor(sessionManager) {
this.sessionManager = sessionManager;
}
// 认证中间件
requireAuth() {
return async (req, res, next) => {
try {
const token = this.extractToken(req);
if (!token) {
return res.status(401).json({
error: 'No token provided',
code: 'NO_TOKEN'
});
}
const session = await this.sessionManager.validateSession(token);
if (!session) {
return res.status(401).json({
error: 'Invalid or expired token',
code: 'INVALID_TOKEN'
});
}
req.user = session;
next();
} catch (error) {
res.status(401).json({
error: 'Authentication failed',
code: 'AUTH_FAILED'
});
}
};
}
// 可选认证
optionalAuth() {
return async (req, res, next) => {
try {
const token = this.extractToken(req);
if (token) {
const session = await this.sessionManager.validateSession(token);
if (session) {
req.user = session;
}
}
next();
} catch (error) {
next();
}
};
}
// 角色权限检查
requireRole(allowedRoles) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
const userRole = req.user.role || 'user';
const roles = Array.isArray(allowedRoles) ? allowedRoles : [allowedRoles];
if (!roles.includes(userRole)) {
return res.status(403).json({
error: 'Insufficient permissions',
code: 'FORBIDDEN'
});
}
next();
};
}
// 提取token
extractToken(req) {
const authHeader = req.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) {
return authHeader.substring(7);
}
return req.cookies?.session || req.query?.token;
}
// CSRF保护
csrfProtection() {
return (req, res, next) => {
if (req.method === 'GET' || req.method === 'HEAD') {
return next();
}
const csrfToken = req.headers['x-csrf-token'];
const sessionToken = req.cookies['csrf-token'];
if (!csrfToken || csrfToken !== sessionToken) {
return res.status(403).json({
error: 'CSRF token validation failed',
code: 'CSRF_FAILED'
});
}
next();
};
}
}
module.exports = AuthMiddleware;
2.5 认证路由 (src/routes/auth.js)
javascript
const express = require('express');
const UserModel = require('../models/user');
const SessionManager = require('../auth/session');
const CryptoUtils = require('../auth/crypto');
function createAuthRouter(sessionManager) {
const router = express.Router();
const userModel = new UserModel();
// 注册
router.post('/register', async (req, res) => {
try {
const { email, password, name } = req.body;
// 验证输入
if (!email || !password || !name) {
return res.status(400).json({
error: 'Email, password, and name are required'
});
}
if (password.length < 6) {
return res.status(400).json({
error: 'Password must be at least 6 characters'
});
}
// 创建用户
const user = await userModel.createUser({ email, password, name });
// 创建会话
const session = await sessionManager.createSession(user.id, user);
res.status(201).json({
message: 'User registered successfully',
user,
session
});
} catch (error) {
res.status(400).json({
error: error.message || 'Registration failed'
});
}
});
// 登录
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({
error: 'Email and password are required'
});
}
// 验证凭据
const user = await userModel.validateCredentials(email, password);
if (!user) {
return res.status(401).json({
error: 'Invalid email or password'
});
}
// 创建会话
const session = await sessionManager.createSession(user.id, user);
// 设置cookie
res.cookie('session', session.sessionId, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 7 * 24 * 60 * 60 * 1000 // 7天
});
res.json({
message: 'Login successful',
user,
session
});
} catch (error) {
res.status(500).json({
error: 'Login failed'
});
}
});
// 登出
router.post('/logout', async (req, res) => {
try {
const token = req.cookies?.session || req.headers.authorization?.substring(7);
if (token) {
await sessionManager.destroySession(token);
}
res.clearCookie('session');
res.json({ message: 'Logout successful' });
} catch (error) {
res.status(500).json({
error: 'Logout failed'
});
}
});
// 验证会话
router.get('/me', async (req, res) => {
try {
const token = req.cookies?.session || req.headers.authorization?.substring(7);
if (!token) {
return res.status(401).json({
error: 'No session token provided'
});
}
const session = await sessionManager.validateSession(token);
if (!session) {
return res.status(401).json({
error: 'Invalid or expired session'
});
}
res.json({
user: session,
expires: session.expires
});
} catch (error) {
res.status(500).json({
error: 'Session validation failed'
});
}
});
// 会话统计
router.get('/stats', async (req, res) => {
try {
const stats = await sessionManager.getSessionStats();
res.json(stats);
} catch (error) {
res.status(500).json({
error: 'Failed to get session stats'
});
}
});
return router;
}
module.exports = createAuthRouter;
2.6 主服务器 (src/server.js)
javascript
const express = require('express');
const cookieParser = require('cookie-parser');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const SessionManager = require('./auth/session');
const AuthMiddleware = require('./auth/middleware');
const createAuthRouter = require('./routes/auth');
class SimpleAuthServer {
constructor(options = {}) {
this.app = express();
this.port = options.port || 3000;
this.sessionManager = new SessionManager(options);
this.authMiddleware = new AuthMiddleware(this.sessionManager);
this.setupMiddleware();
this.setupRoutes();
this.setupErrorHandling();
}
setupMiddleware() {
// 安全中间件
this.app.use(helmet());
this.app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || 'http://localhost:3000',
credentials: true
}));
// 解析中间件
this.app.use(express.json({ limit: '10mb' }));
this.app.use(express.urlencoded({ extended: true }));
this.app.use(cookieParser());
this.app.use(morgan('combined'));
}
setupRoutes() {
// 健康检查
this.app.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// 认证路由
const authRouter = createAuthRouter(this.sessionManager);
this.app.use('/api/auth', authRouter);
// 受保护的路由示例
this.app.get('/api/protected',
this.authMiddleware.requireAuth(),
(req, res) => {
res.json({
message: 'This is a protected route',
user: req.user
});
}
);
// 可选认证路由
this.app.get('/api/profile',
this.authMiddleware.optionalAuth(),
(req, res) => {
if (req.user) {
res.json({
message: 'Welcome back!',
user: req.user
});
} else {
res.json({
message: 'Welcome! Please log in to see personalized content.'
});
}
}
);
// 管理员路由
this.app.get('/api/admin',
this.authMiddleware.requireAuth(),
this.authMiddleware.requireRole(['admin']),
(req, res) => {
res.json({
message: 'Admin dashboard',
user: req.user
});
}
);
}
setupErrorHandling() {
// 404处理
this.app.use((req, res) => {
res.status(404).json({
error: 'Route not found',
path: req.path
});
});
// 全局错误处理
this.app.use((error, req, res, next) => {
console.error('Error:', error);
res.status(error.status || 500).json({
error: error.message || 'Internal server error',
code: error.code || 'INTERNAL_ERROR'
});
});
}
// 会话清理任务
startCleanupTask() {
setInterval(async () => {
try {
const cleaned = await this.sessionManager.cleanupExpiredSessions();
if (cleaned > 0) {
console.log(`Cleaned up ${cleaned} expired sessions`);
}
} catch (error) {
console.error('Session cleanup error:', error);
}
}, 5 * 60 * 1000); // 每5分钟清理一次
}
async start() {
this.startCleanupTask();
this.app.listen(this.port, () => {
console.log(`🚀 SimpleAuth server running on port ${this.port}`);
console.log(`📊 Health check: http://localhost:${this.port}/health`);
console.log(`🔐 Auth endpoints: http://localhost:${this.port}/api/auth`);
});
}
async stop() {
console.log('🛑 Shutting down server...');
process.exit(0);
}
}
// 启动服务器
if (require.main === module) {
require('dotenv').config();
const server = new SimpleAuthServer({
port: process.env.PORT || 3000,
secret: process.env.AUTH_SECRET || 'your-secret-key-change-this'
});
server.start();
// 优雅关闭
process.on('SIGTERM', () => server.stop());
process.on('SIGINT', () => server.stop());
}
module.exports = SimpleAuthServer;
3. 使用示例
3.1 启动服务器
bash
# 安装依赖
npm install express cookie-parser cors helmet morgan bcrypt dotenv
# 创建数据目录
mkdir -p src/data
# 启动服务器
node src/server.js
3.2 测试API
bash
# 注册用户
curl -X POST http://localhost:3000/api/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"password123","name":"Test User"}'
# 用户登录
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"password123"}' \
-c cookies.txt
# 访问受保护路由
curl http://localhost:3000/api/protected \
-b cookies.txt
# 获取用户信息
curl http://localhost:3000/api/auth/me \
-b cookies.txt
# 用户登出
curl -X POST http://localhost:3000/api/auth/logout \
-b cookies.txt
3.3 Node.js客户端使用
javascript
const axios = require('axios');
class SimpleAuthClient {
constructor(baseURL = 'http://localhost:3000') {
this.client = axios.create({
baseURL,
withCredentials: true
});
}
async register(userData) {
const response = await this.client.post('/api/auth/register', userData);
return response.data;
}
async login(credentials) {
const response = await this.client.post('/api/auth/login', credentials);
return response.data;
}
async getProfile() {
const response = await this.client.get('/api/auth/me');
return response.data;
}
async logout() {
const response = await this.client.post('/api/auth/logout');
return response.data;
}
}
// 使用示例
async function example() {
const client = new SimpleAuthClient();
try {
// 注册用户
const user = await client.register({
email: 'demo@example.com',
password: 'demo123',
name: 'Demo User'
});
console.log('Registered:', user);
// 用户登录
const session = await client.login({
email: 'demo@example.com',
password: 'demo123'
});
console.log('Logged in:', session);
// 获取用户信息
const profile = await client.getProfile();
console.log('Profile:', profile);
} catch (error) {
console.error('Error:', error.response?.data || error.message);
}
}
// example();
4. 高级特性
4.1 刷新令牌
javascript
// 添加刷新令牌支持
class RefreshTokenManager {
constructor(sessionManager) {
this.sessionManager = sessionManager;
this.refreshTokens = new Map();
}
async createRefreshToken(userId) {
const refreshToken = CryptoUtils.generateRandomString(40);
const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30天
this.refreshTokens.set(refreshToken, {
userId,
expiresAt,
createdAt: new Date()
});
return refreshToken;
}
async refreshAccessToken(refreshToken) {
const tokenData = this.refreshTokens.get(refreshToken);
if (!tokenData || tokenData.expiresAt < new Date()) {
throw new Error('Invalid or expired refresh token');
}
// 创建新的访问令牌
return await this.sessionManager.createSession(tokenData.userId, {});
}
}
4.2 OAuth集成
javascript
// Google OAuth示例
class GoogleOAuthProvider {
constructor(clientId, clientSecret) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.redirectUri = 'http://localhost:3000/api/auth/callback/google';
}
async getAuthorizationUrl() {
const params = new URLSearchParams({
client_id: this.clientId,
redirect_uri: this.redirectUri,
response_type: 'code',
scope: 'openid email profile',
state: CryptoUtils.generateCSRFToken()
});
return `https://accounts.google.com/oauth2/auth?${params.toString()}`;
}
async exchangeCodeForToken(code) {
const response = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
client_id: this.clientId,
client_secret: this.clientSecret,
code,
redirect_uri: this.redirectUri,
grant_type: 'authorization_code'
})
});
const data = await response.json();
return data.access_token;
}
}
总结
通过本指南,我们深入了解了:
- NextAuth.js核心原理:JWT、Session管理、安全机制
- 完整工作流程:从用户登录到会话验证的每一步
- 安全配置:CSRF保护、密码加密、会话安全
- Node.js原生实现:不依赖任何框架的认证系统
这个Node.js实现包含了:
- ✅ 用户注册/登录
- ✅ JWT令牌管理
- ✅ 会话存储和验证
- ✅ 密码安全加密
- ✅ 中间件保护
- ✅ RESTful API设计
- ✅ 错误处理
- ✅ 会话清理
- ✅ 统计监控