api-authentication

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

API Authentication Expert

API认证专家

Эксперт по аутентификации API с глубокими знаниями протоколов аутентификации, лучших практик безопасности и паттернов реализации.
具备认证协议、安全最佳实践及实现模式深度知识的API认证专家。

Основные методы аутентификации

主要认证方法

API Keys

API Keys

javascript
// Header-based API key
const response = await fetch('/api/data', {
  headers: {
    'X-API-Key': 'your-api-key-here',
    'Content-Type': 'application/json'
  }
});

// Query parameter (менее безопасно)
const response = await fetch('/api/data?api_key=your-api-key');
javascript
// Header-based API key
const response = await fetch('/api/data', {
  headers: {
    'X-API-Key': 'your-api-key-here',
    'Content-Type': 'application/json'
  }
});

// 查询参数(安全性较低)
const response = await fetch('/api/data?api_key=your-api-key');

JWT (JSON Web Tokens)

JWT (JSON Web Tokens)

python
import jwt
from datetime import datetime, timedelta
python
import jwt
from datetime import datetime, timedelta

Генерация JWT

生成JWT

def create_jwt_token(user_id, secret_key): payload = { 'user_id': user_id, 'exp': datetime.utcnow() + timedelta(hours=24), 'iat': datetime.utcnow() } return jwt.encode(payload, secret_key, algorithm='HS256')
def create_jwt_token(user_id, secret_key): payload = { 'user_id': user_id, 'exp': datetime.utcnow() + timedelta(hours=24), 'iat': datetime.utcnow() } return jwt.encode(payload, secret_key, algorithm='HS256')

Верификация JWT

验证JWT

def verify_jwt_token(token, secret_key): try: payload = jwt.decode(token, secret_key, algorithms=['HS256']) return payload['user_id'] except jwt.ExpiredSignatureError: return None except jwt.InvalidTokenError: return None
undefined
def verify_jwt_token(token, secret_key): try: payload = jwt.decode(token, secret_key, algorithms=['HS256']) return payload['user_id'] except jwt.ExpiredSignatureError: return None except jwt.InvalidTokenError: return None
undefined

OAuth 2.0 Authorization Code Flow

OAuth 2.0 Authorization Code Flow

javascript
// Шаг 1: Редирект на сервер авторизации
const authUrl = `https://auth.provider.com/oauth/authorize?
  client_id=${clientId}&
  redirect_uri=${redirectUri}&
  response_type=code&
  scope=read:user&
  state=${randomState}`;

// Шаг 2: Обмен кода на access token
async function exchangeCodeForToken(code) {
  const response = await fetch('https://auth.provider.com/oauth/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'Authorization': `Basic ${btoa(`${clientId}:${clientSecret}`)}`
    },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code: code,
      redirect_uri: redirectUri
    })
  });
  return await response.json();
}
javascript
// 步骤1:重定向到授权服务器
const authUrl = `https://auth.provider.com/oauth/authorize?
  client_id=${clientId}&
  redirect_uri=${redirectUri}&
  response_type=code&
  scope=read:user&
  state=${randomState}`;

// 步骤2:用授权码交换Access Token
async function exchangeCodeForToken(code) {
  const response = await fetch('https://auth.provider.com/oauth/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'Authorization': `Basic ${btoa(`${clientId}:${clientSecret}`)}`
    },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code: code,
      redirect_uri: redirectUri
    })
  });
  return await response.json();
}

Безопасное хранение токенов

令牌安全存储

HttpOnly Cookies

HttpOnly Cookies

javascript
// Server-side cookie configuration
res.cookie('refreshToken', refreshToken, {
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production',
  sameSite: 'strict',
  maxAge: 7 * 24 * 60 * 60 * 1000 // 7 дней
});
javascript
// 服务端Cookie配置
res.cookie('refreshToken', refreshToken, {
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production',
  sameSite: 'strict',
  maxAge: 7 * 24 * 60 * 60 * 1000 // 7天
});

Рекомендации

建议

  • Всегда используйте HTTPS для передачи токенов
  • Храните refresh токены в HttpOnly cookies
  • Access токены храните в памяти (не в localStorage)
  • Используйте короткоживущие access токены (15-60 минут)
  • 始终使用HTTPS传输令牌
  • 将Refresh令牌存储在HttpOnly Cookie中
  • Access令牌存储在内存中(不要存在localStorage)
  • 使用短生命周期的Access令牌(15-60分钟)

Rate Limiting

请求频率限制

python
from functools import wraps
from flask import request, jsonify
from time import time

def rate_limit(max_requests=100, window=3600):
    def decorator(f):
        requests_store = {}

        @wraps(f)
        def decorated_function(*args, **kwargs):
            client_ip = request.remote_addr
            current_time = time()

            # Очистка старых записей
            requests_store[client_ip] = [
                t for t in requests_store.get(client_ip, [])
                if current_time - t < window
            ]

            if len(requests_store.get(client_ip, [])) >= max_requests:
                return jsonify({'error': 'Rate limit exceeded'}), 429

            requests_store.setdefault(client_ip, []).append(current_time)
            return f(*args, **kwargs)
        return decorated_function
    return decorator
python
from functools import wraps
from flask import request, jsonify
from time import time

def rate_limit(max_requests=100, window=3600):
    def decorator(f):
        requests_store = {}

        @wraps(f)
        def decorated_function(*args, **kwargs):
            client_ip = request.remote_addr
            current_time = time()

            # 清理旧记录
            requests_store[client_ip] = [
                t for t in requests_store.get(client_ip, [])
                if current_time - t < window
            ]

            if len(requests_store.get(client_ip, [])) >= max_requests:
                return jsonify({'error': 'Rate limit exceeded'}), 429

            requests_store.setdefault(client_ip, []).append(current_time)
            return f(*args, **kwargs)
        return decorated_function
    return decorator

Middleware аутентификации

认证中间件

Go

Go

go
func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token == "" {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }

        // Remove "Bearer " prefix
        if strings.HasPrefix(token, "Bearer ") {
            token = token[7:]
        }

        userID, err := validateJWT(token)
        if err != nil {
            http.Error(w, "Invalid token", http.StatusUnauthorized)
            return
        }

        ctx := context.WithValue(r.Context(), "userID", userID)
        next(w, r.WithContext(ctx))
    }
}
go
func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token == "" {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }

        // Remove "Bearer " prefix
        if strings.HasPrefix(token, "Bearer ") {
            token = token[7:]
        }

        userID, err := validateJWT(token)
        if err != nil {
            http.Error(w, "Invalid token", http.StatusUnauthorized)
            return
        }

        ctx := context.WithValue(r.Context(), "userID", userID)
        next(w, r.WithContext(ctx))
    }
}

TypeScript/Express

TypeScript/Express

typescript
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';

interface AuthRequest extends Request {
  userId?: string;
}

export const authMiddleware = (
  req: AuthRequest,
  res: Response,
  next: NextFunction
) => {
  const authHeader = req.headers.authorization;

  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing token' });
  }

  const token = authHeader.slice(7);

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET!) as { userId: string };
    req.userId = decoded.userId;
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Invalid token' });
  }
};
typescript
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';

interface AuthRequest extends Request {
  userId?: string;
}

export const authMiddleware = (
  req: AuthRequest,
  res: Response,
  next: NextFunction
) => {
  const authHeader = req.headers.authorization;

  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing token' });
  }

  const token = authHeader.slice(7);

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET!) as { userId: string };
    req.userId = decoded.userId;
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Invalid token' });
  }
};

Token Refresh Strategy

令牌刷新策略

typescript
class TokenManager {
  private accessToken: string | null = null;
  private refreshToken: string | null = null;

  async refreshTokens(): Promise<boolean> {
    try {
      const response = await fetch('/api/auth/refresh', {
        method: 'POST',
        credentials: 'include' // для httpOnly cookies
      });

      if (!response.ok) {
        throw new Error('Token refresh failed');
      }

      const { accessToken } = await response.json();
      this.accessToken = accessToken;
      return true;
    } catch (error) {
      this.logout();
      return false;
    }
  }

  async makeAuthenticatedRequest(url: string, options: RequestInit = {}) {
    if (this.isTokenExpired()) {
      const refreshed = await this.refreshTokens();
      if (!refreshed) {
        throw new Error('Session expired');
      }
    }

    return fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${this.accessToken}`
      }
    });
  }

  private isTokenExpired(): boolean {
    if (!this.accessToken) return true;

    const payload = JSON.parse(atob(this.accessToken.split('.')[1]));
    return payload.exp * 1000 < Date.now();
  }
}
typescript
class TokenManager {
  private accessToken: string | null = null;
  private refreshToken: string | null = null;

  async refreshTokens(): Promise<boolean> {
    try {
      const response = await fetch('/api/auth/refresh', {
        method: 'POST',
        credentials: 'include' // для httpOnly cookies
      });

      if (!response.ok) {
        throw new Error('Token refresh failed');
      }

      const { accessToken } = await response.json();
      this.accessToken = accessToken;
      return true;
    } catch (error) {
      this.logout();
      return false;
    }
  }

  async makeAuthenticatedRequest(url: string, options: RequestInit = {}) {
    if (this.isTokenExpired()) {
      const refreshed = await this.refreshTokens();
      if (!refreshed) {
        throw new Error('Session expired');
      }
    }

    return fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${this.accessToken}`
      }
    });
  }

  private isTokenExpired(): boolean {
    if (!this.accessToken) return true;

    const payload = JSON.parse(atob(this.accessToken.split('.')[1]));
    return payload.exp * 1000 < Date.now();
  }
}

Multi-Factor Authentication (MFA)

多因素认证(MFA)

python
import pyotp
import qrcode
from io import BytesIO

def generate_totp_secret(user_email):
    secret = pyotp.random_base32()
    totp_uri = pyotp.totp.TOTP(secret).provisioning_uri(
        name=user_email,
        issuer_name="Your App Name"
    )

    # Generate QR code
    qr = qrcode.QRCode(version=1, box_size=10, border=5)
    qr.add_data(totp_uri)
    qr.make(fit=True)

    return secret, qr

def verify_totp(secret, token):
    totp = pyotp.TOTP(secret)
    return totp.verify(token, valid_window=1)
python
import pyotp
import qrcode
from io import BytesIO

def generate_totp_secret(user_email):
    secret = pyotp.random_base32()
    totp_uri = pyotp.totp.TOTP(secret).provisioning_uri(
        name=user_email,
        issuer_name="Your App Name"
    )

    # Generate QR code
    qr = qrcode.QRCode(version=1, box_size=10, border=5)
    qr.add_data(totp_uri)
    qr.make(fit=True)

    return secret, qr

def verify_totp(secret, token):
    totp = pyotp.TOTP(secret)
    return totp.verify(token, valid_window=1)

Security Headers

安全标头

javascript
const helmet = require('helmet');
const cors = require('cors');

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
    },
  },
}));

app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
  credentials: true,
  optionsSuccessStatus: 200
}));
javascript
const helmet = require('helmet');
const cors = require('cors');

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
    },
  },
}));

app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
  credentials: true,
  optionsSuccessStatus: 200
}));

Key Rotation

密钥轮换

python
class KeyRotationManager:
    def __init__(self):
        self.current_key_id = self.get_current_key_id()
        self.keys = self.load_signing_keys()

    def sign_token(self, payload):
        key = self.keys[self.current_key_id]
        payload['kid'] = self.current_key_id
        return jwt.encode(payload, key, algorithm='RS256')

    def verify_token(self, token):
        unverified_header = jwt.get_unverified_header(token)
        kid = unverified_header.get('kid')

        if kid not in self.keys:
            raise jwt.InvalidKeyError("Invalid key ID")

        return jwt.decode(token, self.keys[kid], algorithms=['RS256'])
python
class KeyRotationManager:
    def __init__(self):
        self.current_key_id = self.get_current_key_id()
        self.keys = self.load_signing_keys()

    def sign_token(self, payload):
        key = self.keys[self.current_key_id]
        payload['kid'] = self.current_key_id
        return jwt.encode(payload, key, algorithm='RS256')

    def verify_token(self, token):
        unverified_header = jwt.get_unverified_header(token)
        kid = unverified_header.get('kid')

        if kid not in self.keys:
            raise jwt.InvalidKeyError("Invalid key ID")

        return jwt.decode(token, self.keys[kid], algorithms=['RS256'])

Лучшие практики

最佳实践

  1. Используйте HTTPS везде — никогда не передавайте токены по HTTP
  2. Короткоживущие access токены — 15-60 минут максимум
  3. Secure refresh tokens — HttpOnly cookies, ротация при использовании
  4. Валидация на каждом запросе — не кэшируйте результаты авторизации
  5. Логирование событий безопасности — все попытки входа, ошибки токенов
  6. Rate limiting — защита от brute force атак
  7. Ротация ключей — регулярная смена signing keys
  1. 全程使用HTTPS——绝不通过HTTP传输令牌
  2. 短生命周期Access令牌——最长15-60分钟
  3. 安全的Refresh令牌——HttpOnly Cookie存储,使用时轮换
  4. 每次请求都验证——不要缓存授权结果
  5. 安全事件日志——记录所有登录尝试、令牌错误
  6. 请求频率限制——抵御暴力破解攻击
  7. 密钥轮换——定期更换签名密钥