authentication-setup
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAuthentication Setup
认证系统搭建
When to use this skill
技能适用场景
이 스킬을 트리거해야 하는 구체적인 상황을 나열합니다:
- 사용자 로그인 시스템: 새로운 애플리케이션에 사용자 인증 기능을 추가할 때
- API 보안: REST API나 GraphQL API에 인증 레이어를 추가할 때
- 권한 관리: 사용자 역할에 따른 접근 제어가 필요할 때
- 인증 마이그레이션: 기존 인증 시스템을 JWT나 OAuth로 전환할 때
- SSO 통합: Google, GitHub, Microsoft 등의 소셜 로그인을 통합할 때
以下是需要使用该技能的具体场景:
- 用户登录系统:为新应用添加用户认证功能时
- API安全:为REST API或GraphQL API添加认证层时
- 权限管理:需要根据用户角色进行访问控制时
- 认证迁移:将现有认证系统迁移至JWT或OAuth时
- SSO集成:集成Google、GitHub、Microsoft等社交登录时
입력 형식 (Input Format)
输入格式(Input Format)
사용자로부터 받아야 할 입력의 형식과 필수/선택 정보:
需要从用户处获取的输入格式及必填/可选信息:
필수 정보
必填信息
- 인증 방식: JWT, Session, OAuth 2.0 중 선택
- 백엔드 프레임워크: Express, Django, FastAPI, Spring Boot 등
- 데이터베이스: PostgreSQL, MySQL, MongoDB 등
- 보안 요구사항: 비밀번호 정책, 토큰 만료 시간 등
- 认证方式:从JWT、Session、OAuth 2.0中选择
- 后端框架:Express、Django、FastAPI、Spring Boot等
- 数据库:PostgreSQL、MySQL、MongoDB等
- 安全要求:密码策略、令牌过期时间等
선택 정보
可选信息
- MFA 지원: 2FA/MFA 활성화 여부 (기본값: false)
- 소셜 로그인: OAuth 제공자 (Google, GitHub, etc.)
- 세션 저장소: Redis, in-memory 등 (Session 방식인 경우)
- Refresh Token: 사용 여부 (기본값: true)
- MFA支持:是否启用2FA/MFA(默认值:false)
- 社交登录:OAuth提供商(Google、GitHub等)
- 会话存储:Redis、内存存储等(使用Session方式时)
- Refresh Token:是否使用(默认值:true)
입력 예시
输入示例
사용자 인증 시스템을 구축해줘:
- 인증 방식: JWT
- 프레임워크: Express.js + TypeScript
- 데이터베이스: PostgreSQL
- MFA: Google Authenticator 지원
- 소셜 로그인: Google, GitHub
- Refresh Token: 사용请帮我搭建用户认证系统:
- 认证方式: JWT
- 框架: Express.js + TypeScript
- 数据库: PostgreSQL
- MFA: 支持Google Authenticator
- 社交登录: Google, GitHub
- Refresh Token: 使用Instructions
操作步骤
단계별로 정확하게 따라야 할 작업 순서를 명시합니다.
以下是需要严格遵循的分步操作流程:
Step 1: 데이터 모델 설계
步骤1:数据模型设计
사용자 및 인증 관련 데이터베이스 스키마를 설계합니다.
작업 내용:
- User 테이블 설계 (id, email, password_hash, role, created_at, updated_at)
- RefreshToken 테이블 (선택사항)
- OAuthProvider 테이블 (소셜 로그인 사용시)
- 비밀번호는 절대 평문 저장하지 않음 (bcrypt/argon2 해싱 필수)
예시 (PostgreSQL):
sql
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255), -- NULL if OAuth only
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);设计用户及认证相关的数据库架构。
工作内容:
- 设计User表(id、email、password_hash、role、created_at、updated_at)
- RefreshToken表(可选)
- OAuthProvider表(使用社交登录时)
- 绝对禁止明文存储密码(必须使用bcrypt/argon2进行哈希)
示例(PostgreSQL):
sql
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);Step 2: 비밀번호 보안 구현
步骤2:密码安全实现
비밀번호 해싱 및 검증 로직을 구현합니다.
작업 내용:
- bcrypt (Node.js) 또는 argon2 (Python) 사용
- Salt rounds 최소 10 이상 설정
- 비밀번호 강도 검증 (최소 8자, 대소문자, 숫자, 특수문자)
판단 기준:
- Node.js 프로젝트 → bcrypt 라이브러리 사용
- Python 프로젝트 → argon2-cffi 또는 passlib 사용
- 성능이 중요한 경우 → bcrypt 선택
- 최고 보안이 필요한 경우 → argon2 선택
예시 (Node.js + TypeScript):
typescript
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 12;
export async function hashPassword(password: string): Promise<string> {
// 비밀번호 강도 검증
if (password.length < 8) {
throw new Error('Password must be at least 8 characters');
}
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('Password must contain uppercase, lowercase, number, and special character');
}
return await bcrypt.hash(password, SALT_ROUNDS);
}
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
return await bcrypt.compare(password, hash);
}实现密码哈希及验证逻辑。
工作内容:
- 使用bcrypt(Node.js)或argon2(Python)
- Salt rounds至少设置为10
- 密码强度验证(至少8位,包含大小写字母、数字、特殊字符)
选择标准:
- Node.js项目 → 使用bcrypt库
- Python项目 → 使用argon2-cffi或passlib
- 注重性能时 → 选择bcrypt
- 追求最高安全性时 → 选择argon2
示例(Node.js + TypeScript):
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);
}Step 3: JWT 토큰 생성 및 검증
步骤3:JWT令牌生成与验证
JWT 기반 인증을 위한 토큰 시스템을 구현합니다.
작업 내용:
- Access Token (짧은 만료 시간: 15분)
- Refresh Token (긴 만료 시간: 7일~30일)
- JWT 서명에 강력한 SECRET 키 사용 (환경변수로 관리)
- 토큰 페이로드에 최소 정보만 포함 (user_id, role)
예시 (Node.js):
typescript
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;
}实现基于JWT的认证令牌系统。
工作内容:
- Access Token(短过期时间:15分钟)
- Refresh Token(长过期时间:7天~30天)
- 使用高强度SECRET密钥进行JWT签名(通过环境变量管理)
- 令牌载荷仅包含必要信息(user_id、role)
示例(Node.js):
typescript
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;
}Step 4: 인증 미들웨어 구현
步骤4:认证中间件实现
API 요청을 보호하는 인증 미들웨어를 작성합니다.
확인 사항:
- Authorization 헤더에서 Bearer 토큰 추출
- 토큰 검증 및 만료 확인
- 유효한 토큰인 경우 req.user에 사용자 정보 추가
- 에러 처리 (401 Unauthorized)
예시 (Express.js):
typescript
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 TOKEN
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
try {
const payload = verifyAccessToken(token);
req.user = payload;
next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token expired' });
}
return res.status(403).json({ error: 'Invalid token' });
}
}
// Role-based authorization middleware
export function requireRole(...roles: string[]) {
return (req: AuthRequest, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}编写用于保护API请求的认证中间件。
检查项:
- 从Authorization头中提取Bearer令牌
- 验证令牌及检查过期时间
- 令牌有效时将用户信息添加至req.user
- 错误处理(401 Unauthorized)
示例(Express.js):
typescript
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 TOKEN
if (!token) {
return res.status(401).json({ error: '需要Access Token' });
}
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();
};
}Step 5: 인증 API 엔드포인트 구현
步骤5:认证API端点实现
회원가입, 로그인, 토큰 갱신 등의 API를 작성합니다.
작업 내용:
- POST /auth/register - 회원가입
- POST /auth/login - 로그인
- POST /auth/refresh - 토큰 갱신
- POST /auth/logout - 로그아웃
- GET /auth/me - 현재 사용자 정보
예시:
typescript
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: 'Email already exists' });
}
// 비밀번호 해싱
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
});
// Refresh token DB 저장
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: 'Invalid credentials' });
}
// 비밀번호 확인
const isValid = await verifyPassword(password, user.password_hash);
if (!isValid) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// 토큰 생성
const accessToken = generateAccessToken({
userId: user.id,
email: user.email,
role: user.role
});
const refreshToken = generateRefreshToken({
userId: user.id,
email: user.email,
role: user.role
});
// Refresh token 저장
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: 'Refresh token required' });
}
// Refresh token 검증
const payload = verifyRefreshToken(refreshToken);
// DB에서 토큰 확인
const storedToken = await db.refreshToken.findUnique({
where: { token: refreshToken }
});
if (!storedToken || storedToken.expires_at < new Date()) {
return res.status(403).json({ error: 'Invalid or expired refresh token' });
}
// 새 Access token 생성
const accessToken = generateAccessToken({
userId: payload.userId,
email: payload.email,
role: payload.role
});
res.json({ accessToken });
} catch (error) {
res.status(403).json({ error: 'Invalid refresh token' });
}
});
// 현재 사용자 정보
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;实现注册、登录、令牌刷新等API。
工作内容:
- POST /auth/register - 用户注册
- POST /auth/login - 用户登录
- POST /auth/refresh - 令牌刷新
- POST /auth/logout - 用户登出
- GET /auth/me - 获取当前用户信息
示例:
typescript
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
});
// 存储Refresh Token到数据库
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
});
// 存储Refresh Token
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: '需要Refresh Token' });
}
// 验证Refresh Token
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: 'Refresh Token无效或已过期' });
}
// 生成新的Access Token
const accessToken = generateAccessToken({
userId: payload.userId,
email: payload.email,
role: payload.role
});
res.json({ accessToken });
} catch (error) {
res.status(403).json({ error: '无效的Refresh Token' });
}
});
// 获取当前用户信息
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;Output format
输出格式
결과물이 따라야 할 정확한 형식을 정의합니다.
定义结果需遵循的标准格式。
기본 구조
基础结构
프로젝트 디렉토리/
├── src/
│ ├── auth/
│ │ ├── password.ts # 비밀번호 해싱/검증
│ │ ├── jwt.ts # JWT 토큰 생성/검증
│ │ ├── middleware.ts # 인증 미들웨어
│ │ └── routes.ts # 인증 API 엔드포인트
│ ├── models/
│ │ └── User.ts # 사용자 모델
│ └── database/
│ └── schema.sql # 데이터베이스 스키마
├── .env.example # 환경변수 템플릿
└── README.md # 인증 시스템 문서项目目录/
├── src/
│ ├── auth/
│ │ ├── password.ts # 密码哈希/验证
│ │ ├── jwt.ts # JWT令牌生成/验证
│ │ ├── middleware.ts # 认证中间件
│ │ └── routes.ts # 认证API端点
│ ├── models/
│ │ └── User.ts # 用户模型
│ └── database/
│ └── schema.sql # 数据库架构
├── .env.example # 环境变量模板
└── README.md # 认证系统文档환경변수 파일 (.env.example)
环境变量文件(.env.example)
bash
undefinedbash
undefinedJWT Secrets (MUST change in production)
JWT密钥(生产环境必须修改)
ACCESS_TOKEN_SECRET=your-access-token-secret-min-32-characters
REFRESH_TOKEN_SECRET=your-refresh-token-secret-min-32-characters
ACCESS_TOKEN_SECRET=your-access-token-secret-min-32-characters
REFRESH_TOKEN_SECRET=your-refresh-token-secret-min-32-characters
Database
数据库
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
OAuth (Optional)
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
undefinedGOOGLE_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
undefinedConstraints
约束规则
반드시 지켜야 할 규칙과 금지 사항을 명시합니다.
明确必须遵守的规则与禁止事项。
필수 규칙 (MUST)
必须遵守的规则(MUST)
-
비밀번호 보안: 절대 평문으로 저장하지 않음
- bcrypt, argon2 등 검증된 해싱 알고리즘 사용
- Salt rounds 최소 10 이상
-
환경변수 관리: 모든 시크릿 키는 환경변수로 관리
- .env 파일은 .gitignore에 추가
- .env.example로 필요한 변수 목록 제공
-
토큰 만료: Access Token은 짧게 (15분), Refresh Token은 적절히 (7일)
- 보안과 UX의 균형 고려
- Refresh Token은 DB에 저장하여 무효화 가능하게
-
密码安全:绝对禁止明文存储密码
- 使用bcrypt、argon2等经过验证的哈希算法
- Salt rounds至少设置为10
-
环境变量管理:所有密钥通过环境变量管理
- 将.env文件添加至.gitignore
- 提供.env.example文件列出所需变量
-
令牌过期:Access Token设置为短期(15分钟),Refresh Token设置为合理期限(7天)
- 平衡安全性与用户体验
- Refresh Token需存储至数据库以便失效
금지 사항 (MUST NOT)
禁止事项(MUST NOT)
-
평문 비밀번호: 절대 비밀번호를 평문으로 저장하거나 로그에 출력하지 않음
- 심각한 보안 위험
- 법적 책임 문제
-
JWT SECRET 하드코딩: 코드에 SECRET 키를 직접 작성하지 않음
- GitHub에 노출될 위험
- 프로덕션 보안 취약점
-
민감정보 토큰 포함: JWT 페이로드에 비밀번호, 카드번호 등 민감정보 포함 금지
- JWT는 디코딩 가능 (암호화 아님)
- 최소한의 정보만 포함 (user_id, role)
-
明文密码:绝对禁止明文存储密码或在日志中输出
- 存在严重安全风险
- 可能引发法律责任
-
JWT密钥硬编码:禁止在代码中直接写入SECRET密钥
- 存在泄露至GitHub的风险
- 导致生产环境安全漏洞
-
令牌包含敏感信息:禁止在JWT载荷中包含密码、银行卡号等敏感信息
- JWT可被解码(并非加密)
- 仅包含必要信息(user_id、role)
보안 규칙
安全规则
- Rate Limiting: 로그인 API에 rate limiting 적용 (brute force 방지)
- HTTPS 필수: 프로덕션 환경에서는 HTTPS만 사용
- CORS 설정: 허용된 도메인만 API 접근 가능하도록 설정
- Input Validation: 모든 사용자 입력 검증 (SQL Injection, XSS 방지)
- 速率限制:为登录API添加速率限制(防止暴力破解)
- 强制HTTPS:生产环境仅使用HTTPS
- CORS配置:仅允许指定域名访问API
- 输入验证:验证所有用户输入(防止SQL注入、XSS攻击)
Examples
使用示例
실제 사용 사례를 통해 스킬의 적용 방법을 보여줍니다.
通过实际案例展示技能的应用方法。
예시 1: Express.js + PostgreSQL JWT 인증
示例1:Express.js + PostgreSQL JWT认证
상황: Node.js Express 앱에 JWT 기반 사용자 인증 추가
사용자 요청:
Express.js 앱에 JWT 인증을 추가해줘. PostgreSQL 사용하고,
access token은 15분, refresh token은 7일로 설정해줘.스킬 적용 과정:
-
패키지 설치:bash
npm install jsonwebtoken bcrypt pg npm install --save-dev @types/jsonwebtoken @types/bcrypt -
데이터베이스 스키마 생성 (위의 SQL 사용)
-
환경변수 설정:bash
ACCESS_TOKEN_SECRET=$(openssl rand -base64 32) REFRESH_TOKEN_SECRET=$(openssl rand -base64 32) -
인증 모듈 구현 (위의 코드 예시 사용)
-
API 라우트 연결:typescript
import authRoutes from './auth/routes'; app.use('/api/auth', authRoutes);
최종 결과: JWT 기반 인증 시스템 완성, 회원가입/로그인/토큰갱신 API 동작
场景:为Node.js Express应用添加基于JWT的用户认证
用户请求:
请为我的Express.js应用添加JWT认证,使用PostgreSQL数据库,
Access Token过期时间设为15分钟,Refresh Token设为7天。技能应用流程:
-
安装依赖:bash
npm install jsonwebtoken bcrypt pg npm install --save-dev @types/jsonwebtoken @types/bcrypt -
创建数据库架构(使用上述SQL示例)
-
设置环境变量:bash
ACCESS_TOKEN_SECRET=$(openssl rand -base64 32) REFRESH_TOKEN_SECRET=$(openssl rand -base64 32) -
实现认证模块(使用上述代码示例)
-
连接API路由:typescript
import authRoutes from './auth/routes'; app.use('/api/auth', authRoutes);
最终结果:完成JWT认证系统搭建,注册/登录/令牌刷新API可正常运行
예시 2: Role-Based Access Control (RBAC)
示例2:基于角色的访问控制(RBAC)
상황: 관리자와 일반 사용자를 구분하는 권한 시스템
사용자 요청:
관리자만 접근 가능한 API를 만들어줘.
일반 사용자는 403 에러를 받아야 해.최종 결과:
typescript
// 관리자 전용 API
router.delete('/users/:id',
authenticateToken, // 인증 확인
requireRole('admin'), // 역할 확인
async (req, res) => {
// 사용자 삭제 로직
await db.user.delete({ where: { id: req.params.id } });
res.json({ message: 'User deleted' });
}
);
// 사용 예시
// 일반 사용자(role: 'user') 요청 → 403 Forbidden
// 관리자(role: 'admin') 요청 → 200 OK场景:构建区分管理员与普通用户的权限系统
用户请求:
请创建仅管理员可访问的API,
普通用户访问时需返回403错误。最终结果:
typescript
// 管理员专用API
router.delete('/users/:id',
authenticateToken, // 验证认证
requireRole('admin'), // 验证角色
async (req, res) => {
// 用户删除逻辑
await db.user.delete({ where: { id: req.params.id } });
res.json({ message: '用户已删除' });
}
);
// 使用示例
// 普通用户(role: 'user')请求 → 403 Forbidden
// 管理员用户(role: 'admin')请求 → 200 OKBest practices
最佳实践
효과적으로 이 스킬을 사용하기 위한 권장사항입니다.
有效使用该技能的建议。
품질 향상
提升安全性
-
Password Rotation Policy: 주기적인 비밀번호 변경 권장
- 90일마다 변경 알림
- 이전 5개 비밀번호 재사용 방지
- 사용자 경험과 보안의 균형
-
Multi-Factor Authentication (MFA): 중요 계정에 2FA 적용
- Google Authenticator, Authy 등 TOTP 앱 사용
- SMS는 보안성 낮음 (SIM swapping 위험)
- Backup codes 제공
-
Audit Logging: 모든 인증 이벤트 로깅
- 로그인 성공/실패, IP 주소, User Agent 기록
- 이상 탐지 및 사후 분석
- GDPR 준수 (민감정보 제외)
-
密码轮换策略:定期更换密码
- 每90天提醒用户更换
- 禁止重复使用最近5个密码
- 平衡用户体验与安全性
-
多因素认证(MFA):为重要账号启用2FA
- 使用Google Authenticator、Authy等TOTP应用
- 避免使用SMS(存在SIM卡劫持风险)
- 提供备用验证码
-
审计日志:记录所有认证事件
- 记录登录成功/失败、IP地址、User Agent
- 用于异常检测与事后分析
- 符合GDPR要求(排除敏感信息)
효율성 개선
提升效率
- Token Blacklist: 로그아웃 시 Refresh Token 무효화
- Redis Caching: 자주 사용하는 사용자 정보 캐싱
- Database Indexing: email, refresh_token에 인덱스 추가
- 令牌黑名单:登出时失效Refresh Token
- Redis缓存:缓存常用用户信息
- 数据库索引:为email、refresh_token字段添加索引
자주 발생하는 문제 (Common Issues)
常见问题(Common Issues)
흔히 발생하는 문제와 해결 방법입니다.
常见问题及解决方法。
문제 1: "JsonWebTokenError: invalid signature"
问题1:"JsonWebTokenError: invalid signature"
증상:
- 토큰 검증 시 에러 발생
- 로그인은 되지만 인증된 API 호출 실패
원인:
Access Token과 Refresh Token의 SECRET 키가 다른데,
같은 키로 검증하려고 시도
해결방법:
- 환경변수 확인: ,
ACCESS_TOKEN_SECRETREFRESH_TOKEN_SECRET - 각 토큰 타입에 맞는 SECRET 사용
- 환경변수가 제대로 로드되는지 확인 (초기화)
dotenv
症状:
- 验证令牌时触发错误
- 可正常登录但无法访问认证API
原因:
Access Token与Refresh Token使用不同的SECRET密钥,却使用同一密钥进行验证
解决方法:
- 检查环境变量:、
ACCESS_TOKEN_SECRETREFRESH_TOKEN_SECRET - 为每种令牌类型使用对应的SECRET密钥
- 确认环境变量已正确加载(检查初始化)
dotenv
문제 2: CORS 에러로 프론트엔드에서 로그인 불가
问题2:前端因CORS错误无法登录
증상: 브라우저 콘솔에 "CORS policy" 에러
원인: Express 서버에 CORS 설정 누락
해결방법:
typescript
import cors from 'cors';
app.use(cors({
origin: process.env.FRONTEND_URL || 'http://localhost:3000',
credentials: true
}));症状:浏览器控制台显示"CORS policy"错误
原因:Express服务器未配置CORS
解决方法:
typescript
import cors from 'cors';
app.use(cors({
origin: process.env.FRONTEND_URL || 'http://localhost:3000',
credentials: true
}));문제 3: Refresh Token이 계속 만료됨
问题3:Refresh Token频繁过期
증상: 사용자가 자주 로그아웃되는 현상
원인: Refresh Token이 DB에서 제대로 관리되지 않음
해결방법:
- Refresh Token 생성 시 DB에 저장 확인
- 만료 시간 적절히 설정 (최소 7일)
- 만료된 토큰 정기적으로 정리하는 cron job 추가
症状:用户频繁被登出
原因:Refresh Token未在数据库中正确管理
解决方法:
- 确认生成Refresh Token时已存储至数据库
- 合理设置过期时间(至少7天)
- 添加定时任务定期清理过期令牌
References
参考资料
공식 문서
官方文档
라이브러리
相关库
- jsonwebtoken (Node.js)
- bcrypt (Node.js)
- Passport.js - 다양한 인증 전략
- NextAuth.js - Next.js 인증
- jsonwebtoken (Node.js)
- bcrypt (Node.js)
- Passport.js - 多种认证策略
- NextAuth.js - Next.js认证方案
보안 가이드
安全指南
Metadata
元数据
버전
版本
- 현재 버전: 1.0.0
- 최종 업데이트: 2025-01-01
- 호환 플랫폼: Claude, ChatGPT, Gemini
- 当前版本: 1.0.0
- 最后更新: 2025-01-01
- 兼容平台: Claude, ChatGPT, Gemini
관련 스킬
相关技能
- api-design: API 엔드포인트 설계
- security: 보안 베스트 프랙티스
- api-design: API端点设计
- security: 安全最佳实践
태그
标签
#authentication#authorization#JWT#OAuth#security#backend#authentication#authorization#JWT#OAuth#security#backend