🚀 快速安装

复制以下命令并运行,立即安装此 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

约束条件

指定强制性规则和禁止行为。

必需规则(必须遵守)

  1. 密码安全:切勿以明文存储密码
    • 使用经过验证的哈希算法,如 bcrypt 或 argon2
    • 盐轮数至少为 10
  2. 环境变量管理:通过环境变量管理所有密钥
    • 将 .env 文件添加到 .gitignore
    • 通过 .env.example 提供所需变量的列表
  3. 令牌过期:访问令牌应短生命周期(15 分钟),刷新令牌适当延长(7 天)
    • 平衡安全性和用户体验
    • 在数据库中存储刷新令牌以实现撤销功能

禁止行为(不得违反)

  1. 明文密码:切勿以明文形式存储密码或将其打印到日志中
    • 严重的安全风险
    • 法律追责问题
  2. 硬编码 JWT 密钥:不要直接在代码中写入密钥
    • 存在在 GitHub 上暴露的风险
    • 生产环境安全漏洞
  3. 令牌中的敏感数据:不要在 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 天。

技能应用过程

  1. 安装包:
    npm install jsonwebtoken bcrypt pg
    npm install --save-dev @types/jsonwebtoken @types/bcrypt
    
  2. 创建数据库模式(使用上面的 SQL)
  3. 设置环境变量:
    ACCESS_TOKEN_SECRET=$(openssl rand -base64 32)
    REFRESH_TOKEN_SECRET=$(openssl rand -base64 32)
    
  4. 实现认证模块(使用上面的代码示例)
  5. 连接 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

最佳实践

有效使用此技能的建议。

质量提升

  1. 密码轮换策略:建议定期更改密码
    • 每 90 天通知更改一次
    • 防止重用最近 5 个密码
    • 平衡用户体验和安全性
  2. 多因素认证:对重要账户应用双因素认证
    • 使用 Google Authenticator 或 Authy 等 TOTP 应用
    • SMS 安全性较低(存在 SIM 卡交换风险)
    • 提供备用代码
  3. 审计日志:记录所有认证事件
    • 记录登录成功/失败、IP 地址和用户代理
    • 异常检测和事后分析
    • GDPR 合规(排除敏感数据)

效率提升

  • 令牌黑名单:登出时撤销刷新令牌
  • Redis 缓存:缓存常用用户数据
  • 数据库索引:在 email 和 refresh_token 上添加索引

常见问题

常见问题及其解决方案。

问题 1:“JsonWebTokenError: invalid signature”

症状

  • 令牌验证期间发生错误
  • 登录成功,但需要认证的 API 调用失败

原因
访问令牌和刷新令牌的密钥不同,
但正在使用相同的密钥来验证两者。

解决方案

  1. 检查环境变量:ACCESS_TOKEN_SECRETREFRESH_TOKEN_SECRET
  2. 为每种令牌类型使用正确的密钥
  3. 验证环境变量是否正确加载(初始化 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:刷新令牌不断过期

症状:用户频繁被登出

原因:刷新令牌在数据库中未正确管理

解决方案

  1. 确认刷新令牌在创建时保存到数据库
  2. 设置适当的过期时间(至少 7 天)
  3. 添加定时任务定期清理过期的令牌

参考资料

官方文档

安全指南

元数据

版本

  • 当前版本:1.0.0
  • 最后更新:2025-01-01
  • 兼容平台:Claude, ChatGPT, Gemini

相关技能

标签

#认证 #授权 #JWT #OAuth #安全 #后端

📄 原始文档

完整文档(英文):

https://skills.sh/supercent-io/skills-template/authentication-setup

💡 提示:点击上方链接查看 skills.sh 原始英文文档,方便对照翻译。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。