🚀 快速安装
复制以下命令并运行,立即安装此 Skill:
npx @anthropic-ai/skills install supercent-io/skills-template/authentication-setup
💡 提示:需要 Node.js 和 NPM
认证设置
何时使用此技能
列出应触发此技能的具体情况:
- 用户登录系统:当为新应用程序添加用户认证时
- API 安全:当为 REST 或 GraphQL API 添加认证层时
- 权限管理:当需要基于角色的访问控制时
- 认证迁移:当将现有认证系统迁移到 JWT 或 OAuth 时
- SSO 集成:当集成 Google、GitHub、Microsoft 等社交登录时
输入格式
需要从用户收集的必需和可选输入信息:
必需信息
- 认证方法:从 JWT、Session 或 OAuth 2.0 中选择
- 后端框架:Express、Django、FastAPI、Spring Boot 等
- 数据库:PostgreSQL、MySQL、MongoDB 等
- 安全要求:密码策略、令牌过期时间等
可选信息
- MFA 支持:是否启用双因素认证/多因素认证(默认:否)
- 社交登录:OAuth 提供商(Google、GitHub 等)
- 会话存储:Redis、内存等(如果使用会话)
- 刷新令牌:是否使用(默认:是)
输入示例
构建一个用户认证系统:
- 认证方法:JWT
- 框架:Express.js + TypeScript
- 数据库:PostgreSQL
- 多因素认证:支持 Google Authenticator
- 社交登录:Google, GitHub
- 刷新令牌:启用
指示
指定要精确遵循的逐步任务序列。
步骤 1:设计数据模型
为用户和认证设计数据库模式。
任务:
- 设计用户表(id、email、password_hash、role、created_at、updated_at)
- 刷新令牌表(可选)
- OAuth 提供商表(如果使用社交登录)
- 切勿以明文存储密码(必须使用 bcrypt/argon2 哈希)
示例(PostgreSQL):
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255), -- 如果仅使用 OAuth,则为 NULL
role VARCHAR(50) DEFAULT 'user',
is_verified BOOLEAN DEFAULT false,
mfa_secret VARCHAR(255),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE refresh_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
token VARCHAR(500) UNIQUE NOT NULL,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_refresh_tokens_user_id ON refresh_tokens(user_id);
步骤 2:实现密码安全
实现密码哈希和验证逻辑。
任务:
- 使用 bcrypt(Node.js)或 argon2(Python)
- 设置盐轮数至少为 10
- 密码强度验证(至少 8 个字符,大小写字母、数字、特殊字符)
决策标准:
- Node.js 项目 → 使用 bcrypt 库
- Python 项目 → 使用 argon2-cffi 或 passlib
- 性能关键场景 → 选择 bcrypt
- 需要最高安全性的场景 → 选择 argon2
示例(Node.js + TypeScript):
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 12;
export async function hashPassword(password: string): Promise<string> {
// 验证密码强度
if (password.length < 8) {
throw new Error('密码长度至少为 8 个字符');
}
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumber = /\d/.test(password);
const hasSpecial = /[!@#$%^&*(),.?":{}|<>]/.test(password);
if (!hasUpperCase || !hasLowerCase || !hasNumber || !hasSpecial) {
throw new Error('密码必须包含大写字母、小写字母、数字和特殊字符');
}
return await bcrypt.hash(password, SALT_ROUNDS);
}
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
return await bcrypt.compare(password, hash);
}
步骤 3:生成和验证 JWT 令牌
为基于 JWT 的认证实现令牌系统。
任务:
- 访问令牌(短过期时间:15 分钟)
- 刷新令牌(长过期时间:7–30 天)
- 为 JWT 签名使用强 SECRET 密钥(通过环境变量管理)
- 令牌负载中仅包含最必要的信息(user_id、role)
示例(Node.js):
import jwt from 'jsonwebtoken';
const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET!;
const REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET!;
const ACCESS_TOKEN_EXPIRY = '15m';
const REFRESH_TOKEN_EXPIRY = '7d';
interface TokenPayload {
userId: string;
email: string;
role: string;
}
export function generateAccessToken(payload: TokenPayload): string {
return jwt.sign(payload, ACCESS_TOKEN_SECRET, {
expiresIn: ACCESS_TOKEN_EXPIRY,
issuer: 'your-app-name',
audience: 'your-app-users'
});
}
export function generateRefreshToken(payload: TokenPayload): string {
return jwt.sign(payload, REFRESH_TOKEN_SECRET, {
expiresIn: REFRESH_TOKEN_EXPIRY,
issuer: 'your-app-name',
audience: 'your-app-users'
});
}
export function verifyAccessToken(token: string): TokenPayload {
return jwt.verify(token, ACCESS_TOKEN_SECRET, {
issuer: 'your-app-name',
audience: 'your-app-users'
}) as TokenPayload;
}
export function verifyRefreshToken(token: string): TokenPayload {
return jwt.verify(token, REFRESH_TOKEN_SECRET, {
issuer: 'your-app-name',
audience: 'your-app-users'
}) as TokenPayload;
}
步骤 4:实现认证中间件
编写认证中间件以保护 API 请求。
检查清单:
- 从 Authorization 头中提取 Bearer 令牌
- 验证令牌并检查过期时间
- 将用户信息附加到 req.user 以便有效令牌使用
- 错误处理(401 未授权)
示例(Express.js):
import { Request, Response, NextFunction } from 'express';
import { verifyAccessToken } from './jwt';
export interface AuthRequest extends Request {
user?: {
userId: string;
email: string;
role: string;
};
}
export function authenticateToken(req: AuthRequest, res: Response, next: NextFunction) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer 令牌
if (!token) {
return res.status(401).json({ error: '需要访问令牌' });
}
try {
const payload = verifyAccessToken(token);
req.user = payload;
next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({ error: '令牌已过期' });
}
return res.status(403).json({ error: '无效令牌' });
}
}
// 基于角色的授权中间件
export function requireRole(...roles: string[]) {
return (req: AuthRequest, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: '需要认证' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: '权限不足' });
}
next();
};
}
步骤 5:实现认证 API 端点
编写用于注册、登录、令牌刷新等的 API。
任务:
- POST /auth/register – 注册
- POST /auth/login – 登录
- POST /auth/refresh – 令牌刷新
- POST /auth/logout – 登出
- GET /auth/me – 当前用户信息
示例:
import express from 'express';
import { hashPassword, verifyPassword } from './password';
import { generateAccessToken, generateRefreshToken, verifyRefreshToken } from './jwt';
import { authenticateToken } from './middleware';
const router = express.Router();
// 注册
router.post('/register', async (req, res) => {
try {
const { email, password } = req.body;
// 检查重复邮箱
const existingUser = await db.user.findUnique({ where: { email } });
if (existingUser) {
return res.status(409).json({ error: '邮箱已存在' });
}
// 哈希密码
const passwordHash = await hashPassword(password);
// 创建用户
const user = await db.user.create({
data: { email, password_hash: passwordHash, role: 'user' }
});
// 生成令牌
const accessToken = generateAccessToken({
userId: user.id,
email: user.email,
role: user.role
});
const refreshToken = generateRefreshToken({
userId: user.id,
email: user.email,
role: user.role
});
// 在数据库中存储刷新令牌
await db.refreshToken.create({
data: {
user_id: user.id,
token: refreshToken,
expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 7 天
}
});
res.status(201).json({
user: { id: user.id, email: user.email, role: user.role },
accessToken,
refreshToken
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 登录
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
// 查找用户
const user = await db.user.findUnique({ where: { email } });
if (!user || !user.password_hash) {
return res.status(401).json({ error: '凭据无效' });
}
// 验证密码
const isValid = await verifyPassword(password, user.password_hash);
if (!isValid) {
return res.status(401).json({ error: '凭据无效' });
}
// 生成令牌
const accessToken = generateAccessToken({
userId: user.id,
email: user.email,
role: user.role
});
const refreshToken = generateRefreshToken({
userId: user.id,
email: user.email,
role: user.role
});
// 存储刷新令牌
await db.refreshToken.create({
data: {
user_id: user.id,
token: refreshToken,
expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
}
});
res.json({
user: { id: user.id, email: user.email, role: user.role },
accessToken,
refreshToken
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 令牌刷新
router.post('/refresh', async (req, res) => {
try {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(401).json({ error: '需要刷新令牌' });
}
// 验证刷新令牌
const payload = verifyRefreshToken(refreshToken);
// 检查数据库中的令牌
const storedToken = await db.refreshToken.findUnique({
where: { token: refreshToken }
});
if (!storedToken || storedToken.expires_at < new Date()) {
return res.status(403).json({ error: '无效或过期的刷新令牌' });
}
// 生成新的访问令牌
const accessToken = generateAccessToken({
userId: payload.userId,
email: payload.email,
role: payload.role
});
res.json({ accessToken });
} catch (error) {
res.status(403).json({ error: '无效的刷新令牌' });
}
});
// 当前用户信息
router.get('/me', authenticateToken, async (req: AuthRequest, res) => {
try {
const user = await db.user.findUnique({
where: { id: req.user!.userId },
select: { id: true, email: true, role: true, created_at: true }
});
res.json({ user });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
export default router;
输出格式
定义可交付成果应遵循的确切格式。
基本结构
项目目录/
├── src/
│ ├── auth/
│ │ ├── password.ts # 密码哈希/验证
│ │ ├── jwt.ts # JWT 令牌生成/验证
│ │ ├── middleware.ts # 认证中间件
│ │ └── routes.ts # 认证 API 端点
│ ├── models/
│ │ └── User.ts # 用户模型
│ └── database/
│ └── schema.sql # 数据库模式
├── .env.example # 环境变量模板
└── README.md # 认证系统文档
环境变量文件 (.env.example)
# JWT 密钥(生产环境必须更改)
ACCESS_TOKEN_SECRET=your-access-token-secret-min-32-characters
REFRESH_TOKEN_SECRET=your-refresh-token-secret-min-32-characters
# 数据库
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
# OAuth(可选)
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
约束条件
指定强制性规则和禁止行为。
必需规则(必须遵守)
- 密码安全:切勿以明文存储密码
- 使用经过验证的哈希算法,如 bcrypt 或 argon2
- 盐轮数至少为 10
- 环境变量管理:通过环境变量管理所有密钥
- 将 .env 文件添加到 .gitignore
- 通过 .env.example 提供所需变量的列表
- 令牌过期:访问令牌应短生命周期(15 分钟),刷新令牌适当延长(7 天)
- 平衡安全性和用户体验
- 在数据库中存储刷新令牌以实现撤销功能
禁止行为(不得违反)
- 明文密码:切勿以明文形式存储密码或将其打印到日志中
- 严重的安全风险
- 法律追责问题
- 硬编码 JWT 密钥:不要直接在代码中写入密钥
- 存在在 GitHub 上暴露的风险
- 生产环境安全漏洞
- 令牌中的敏感数据:不要在 JWT 负载中包含密码、卡号或其他敏感数据
- JWT 可以被解码(它不是加密的)
- 仅包含最少信息(user_id、role)
安全规则
- 速率限制:对登录 API 应用速率限制(防止暴力破解攻击)
- HTTPS 必需:在生产环境中仅使用 HTTPS
- CORS 配置:仅允许批准的域访问 API
- 输入验证:验证所有用户输入(防止 SQL 注入和 XSS)
示例
通过真实用例演示如何应用此技能。
示例 1:Express.js + PostgreSQL JWT 认证
场景:向 Node.js Express 应用添加基于 JWT 的用户认证
用户请求:
向使用 PostgreSQL 的 Express.js 应用添加 JWT 认证,
访问令牌过期时间为 15 分钟,刷新令牌过期时间为 7 天。
技能应用过程:
- 安装包:
npm install jsonwebtoken bcrypt pg npm install --save-dev @types/jsonwebtoken @types/bcrypt - 创建数据库模式(使用上面的 SQL)
- 设置环境变量:
ACCESS_TOKEN_SECRET=$(openssl rand -base64 32) REFRESH_TOKEN_SECRET=$(openssl rand -base64 32) - 实现认证模块(使用上面的代码示例)
- 连接 API 路由:
import authRoutes from './auth/routes'; app.use('/api/auth', authRoutes);
最终结果:基于 JWT 的认证系统完成,注册/登录/令牌刷新 API 工作正常
示例 2:基于角色的访问控制
场景:一个区分管理员和普通用户的权限系统
用户请求:
创建一个仅管理员可访问的 API。
普通用户应收到 403 错误。
最终结果:
// 仅限管理员的 API
router.delete('/users/:id',
authenticateToken, // 验证认证
requireRole('admin'), // 验证角色
async (req, res) => {
// 用户删除逻辑
await db.user.delete({ where: { id: req.params.id } });
res.json({ message: '用户已删除' });
}
);
// 使用示例
// 普通用户(角色:'user')请求 → 403 禁止访问
// 管理员(角色:'admin')请求 → 200 OK
最佳实践
有效使用此技能的建议。
质量提升
- 密码轮换策略:建议定期更改密码
- 每 90 天通知更改一次
- 防止重用最近 5 个密码
- 平衡用户体验和安全性
- 多因素认证:对重要账户应用双因素认证
- 使用 Google Authenticator 或 Authy 等 TOTP 应用
- SMS 安全性较低(存在 SIM 卡交换风险)
- 提供备用代码
- 审计日志:记录所有认证事件
- 记录登录成功/失败、IP 地址和用户代理
- 异常检测和事后分析
- GDPR 合规(排除敏感数据)
效率提升
- 令牌黑名单:登出时撤销刷新令牌
- Redis 缓存:缓存常用用户数据
- 数据库索引:在 email 和 refresh_token 上添加索引
常见问题
常见问题及其解决方案。
问题 1:“JsonWebTokenError: invalid signature”
症状:
- 令牌验证期间发生错误
- 登录成功,但需要认证的 API 调用失败
原因:
访问令牌和刷新令牌的密钥不同,
但正在使用相同的密钥来验证两者。
解决方案:
- 检查环境变量:
ACCESS_TOKEN_SECRET、REFRESH_TOKEN_SECRET - 为每种令牌类型使用正确的密钥
- 验证环境变量是否正确加载(初始化
dotenv)
问题 2:前端因 CORS 错误无法登录
症状:浏览器控制台中显示“CORS policy”错误
原因:Express 服务器上缺少 CORS 配置
解决方案:
import cors from 'cors';
app.use(cors({
origin: process.env.FRONTEND_URL || 'http://localhost:3000',
credentials: true
}));
问题 3:刷新令牌不断过期
症状:用户频繁被登出
原因:刷新令牌在数据库中未正确管理
解决方案:
- 确认刷新令牌在创建时保存到数据库
- 设置适当的过期时间(至少 7 天)
- 添加定时任务定期清理过期的令牌
参考资料
官方文档
库
- jsonwebtoken (Node.js)
- bcrypt (Node.js)
- Passport.js – 多种认证策略
- NextAuth.js – Next.js 认证
安全指南
元数据
版本
- 当前版本:1.0.0
- 最后更新:2025-01-01
- 兼容平台:Claude, ChatGPT, Gemini
相关技能
- api-design:API 端点设计
- security:安全最佳实践
标签
#认证 #授权 #JWT #OAuth #安全 #后端
📄 原始文档
完整文档(英文):
https://skills.sh/supercent-io/skills-template/authentication-setup
💡 提示:点击上方链接查看 skills.sh 原始英文文档,方便对照翻译。
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

评论(0)