强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

HTTP 协议详解教程 / 第 9 章:认证与授权

第 9 章:认证与授权

认证(Authentication)解决"你是谁"的问题,授权(Authorization)解决"你能做什么"的问题。本章全面介绍 HTTP 中的认证方案。


9.1 认证 vs 授权

概念英文回答的问题HTTP 头部
认证Authentication你是谁?Authorization
授权Authorization你能做什么?Authorization + 权限检查
用户请求 → 认证(你是谁?) → 授权(你有权限吗?) → 处理请求
              ↓ 失败              ↓ 失败
           401 Unauthorized    403 Forbidden

9.2 HTTP Basic 认证

原理

# 服务端返回 401,告知需要认证
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="My Application"

# 客户端发送凭据(Base64 编码的 username:password)
GET /api/data HTTP/1.1
Authorization: Basic dXNlcjpwYXNzd29yZA==

代码实现

import requests
from requests.auth import HTTPBasicAuth

# 方式 1:使用 HTTPBasicAuth
response = requests.get(
    'https://api.example.com/data',
    auth=HTTPBasicAuth('username', 'password')
)

# 方式 2:使用元组简写
response = requests.get(
    'https://api.example.com/data',
    auth=('username', 'password')
)

# 方式 3:手动构建 Header
import base64
credentials = base64.b64encode(b'username:password').decode()
response = requests.get(
    'https://api.example.com/data',
    headers={'Authorization': f'Basic {credentials}'}
)
# curl Basic 认证
curl -u username:password https://api.example.com/data

# 或者
curl -H "Authorization: Basic $(echo -n 'user:pass' | base64)" https://api.example.com/data

服务端实现

const express = require('express');
const app = express();

function basicAuth(req, res, next) {
    const authHeader = req.headers.authorization;
    
    if (!authHeader || !authHeader.startsWith('Basic ')) {
        res.setHeader('WWW-Authenticate', 'Basic realm="My Application"');
        return res.status(401).json({ error: '请提供认证凭据' });
    }
    
    const credentials = Buffer.from(authHeader.slice(6), 'base64').toString();
    const [username, password] = credentials.split(':');
    
    if (username === 'admin' && password === 'secret') {
        req.user = { username };
        next();
    } else {
        res.setHeader('WWW-Authenticate', 'Basic realm="My Application"');
        return res.status(401).json({ error: '凭据无效' });
    }
}

app.get('/api/admin', basicAuth, (req, res) => {
    res.json({ message: `欢迎,${req.user.username}` });
});

优缺点

优点缺点
简单通用每次请求都发送密码
浏览器原生支持Base64 不是加密
无状态凭据无法过期

⚠️ 安全警告:Basic 认证必须配合 HTTPS 使用,否则密码明文传输。


9.3 Bearer Token 认证

原理

GET /api/users HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

完整的 Token 认证流程

1. 登录获取 Token
POST /api/login
{"email": "[email protected]", "password": "P@ssw0rd"}

响应:
{
    "access_token": "eyJhbGci...",
    "refresh_token": "dGhpcyBp...",
    "expires_in": 3600,
    "token_type": "Bearer"
}

2. 使用 Token 访问 API
GET /api/users
Authorization: Bearer eyJhbGci...

3. Token 过期后刷新
POST /api/refresh
{"refresh_token": "dGhpcyBp..."}

服务端实现

const jwt = require('jsonwebtoken');

const ACCESS_SECRET = 'access-secret-key';
const REFRESH_SECRET = 'refresh-secret-key';

// 登录
app.post('/api/login', async (req, res) => {
    const { email, password } = req.body;
    const user = await findUser(email);
    
    if (!user || !await verifyPassword(password, user.hash)) {
        return res.status(401).json({ error: '邮箱或密码错误' });
    }
    
    const accessToken = jwt.sign(
        { sub: user.id, email: user.email, role: user.role },
        ACCESS_SECRET,
        { expiresIn: '1h' }
    );
    
    const refreshToken = jwt.sign(
        { sub: user.id },
        REFRESH_SECRET,
        { expiresIn: '7d' }
    );
    
    await storeRefreshToken(user.id, refreshToken);
    
    res.json({
        access_token: accessToken,
        refresh_token: refreshToken,
        expires_in: 3600,
        token_type: 'Bearer'
    });
});

// 刷新 Token
app.post('/api/refresh', async (req, res) => {
    const { refresh_token } = req.body;
    
    try {
        const payload = jwt.verify(refresh_token, REFRESH_SECRET);
        const stored = await getRefreshToken(payload.sub);
        
        if (stored !== refresh_token) {
            return res.status(401).json({ error: '无效的刷新令牌' });
        }
        
        const newAccessToken = jwt.sign(
            { sub: payload.sub },
            ACCESS_SECRET,
            { expiresIn: '1h' }
        );
        
        res.json({
            access_token: newAccessToken,
            expires_in: 3600,
            token_type: 'Bearer'
        });
    } catch (err) {
        res.status(401).json({ error: '刷新令牌已过期,请重新登录' });
    }
});

// 认证中间件
function authenticate(req, res, next) {
    const authHeader = req.headers.authorization;
    
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
        return res.status(401).json({ error: '请提供认证令牌' });
    }
    
    const token = authHeader.slice(7);
    
    try {
        const payload = jwt.verify(token, ACCESS_SECRET);
        req.user = payload;
        next();
    } catch (err) {
        if (err.name === 'TokenExpiredError') {
            return res.status(401).json({ error: '令牌已过期', code: 'TOKEN_EXPIRED' });
        }
        return res.status(401).json({ error: '无效的令牌' });
    }
}

9.4 JWT(JSON Web Token)

JWT 结构

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.SflKxwRJSMeKKF2QT4fwpM
├──── Header ────┤├──── Payload ────┤├──── Signature ────────┤
部分内容编码
Header算法、类型Base64URL
Payload声明(Claims)Base64URL
Signature签名HMAC/RS256

Payload 常见字段

字段含义示例
sub主题(用户 ID)"12345"
iss签发者"api.example.com"
aud受众"web.example.com"
exp过期时间1715328000
iat签发时间1715324400
nbf生效时间1715324400
jtiJWT ID"unique-id"
// Python JWT 示例
const jwt = require('jsonwebtoken');

// 签发 Token
const token = jwt.sign(
    {
        sub: '12345',
        email: '[email protected]',
        role: 'admin'
    },
    'secret-key',
    {
        algorithm: 'HS256',
        expiresIn: '1h',
        issuer: 'api.example.com'
    }
);

console.log(token);
// eyJhbGciOiJIUzI1NiIs...

// 验证 Token
try {
    const payload = jwt.verify(token, 'secret-key', {
        algorithms: ['HS256'],
        issuer: 'api.example.com'
    });
    console.log(payload);
    // { sub: '12345', email: '[email protected]', role: 'admin', iat: ..., exp: ... }
} catch (err) {
    console.error('Token 无效:', err.message);
}

JWT 安全注意事项

注意说明
不要存敏感信息Payload 只是 Base64,不是加密
使用强密钥HS256 至少 256 位密钥
设置过期时间exp 必须设置
验证所有字段issaudexp 都要验证
短生命周期Access Token 建议 15-60 分钟

9.5 OAuth 2.0

四种授权模式

模式适用场景安全性
授权码模式(Authorization Code)Web 应用最高
授权码 + PKCESPA、移动端
客户端凭据(Client Credentials)机器对机器
隐式模式(Implicit)已废弃

授权码流程

┌──────┐                ┌──────────┐              ┌──────────┐
│Client│                │Auth Server│              │ Resource │
└──┬───┘                └────┬─────┘              └────┬─────┘
   │                        │                          │
   │ 1. 重定向到授权页       │                          │
   │───────────────────────→│                          │
   │                        │                          │
   │ 2. 用户授权            │                          │
   │←──────────────────────│                          │
   │                        │                          │
   │ 3. 用授权码换取 Token   │                          │
   │───────────────────────→│                          │
   │                        │                          │
   │ 4. 返回 Access Token   │                          │
   │←──────────────────────│                          │
   │                        │                          │
   │ 5. 用 Token 访问资源    │                          │
   │──────────────────────────────────────────────────→│
   │                        │                          │
   │ 6. 返回资源            │                          │
   │←─────────────────────────────────────────────────│
// 使用 passport.js 实现 OAuth2
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;

passport.use(new GoogleStrategy({
    clientID: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    callbackURL: '/auth/google/callback'
}, (accessToken, refreshToken, profile, done) => {
    // 查找或创建用户
    User.findOrCreate({ googleId: profile.id }, (err, user) => {
        return done(err, user);
    });
}));

// 路由
app.get('/auth/google', passport.authenticate('google', {
    scope: ['profile', 'email']
}));

app.get('/auth/google/callback',
    passport.authenticate('google', { failureRedirect: '/login' }),
    (req, res) => {
        res.redirect('/');
    }
);

9.6 API Key 认证

传递方式

# 方式 1:查询参数
curl "https://api.example.com/data?api_key=YOUR_API_KEY"

# 方式 2:自定义头部
curl -H "X-API-Key: YOUR_API_KEY" https://api.example.com/data

# 方式 3:Authorization 头
curl -H "Authorization: ApiKey YOUR_API_KEY" https://api.example.com/data

服务端实现

const crypto = require('crypto');

// 生成 API Key
function generateApiKey() {
    return `pk_${crypto.randomBytes(32).toString('hex')}`;
}

// API Key 验证中间件
async function apiKeyAuth(req, res, next) {
    const apiKey = req.headers['x-api-key'] || req.query.api_key;
    
    if (!apiKey) {
        return res.status(401).json({
            error: { code: 'MISSING_API_KEY', message: '请提供 API Key' }
        });
    }
    
    const keyRecord = await findApiKey(apiKey);
    if (!keyRecord) {
        return res.status(401).json({
            error: { code: 'INVALID_API_KEY', message: 'API Key 无效' }
        });
    }
    
    if (keyRecord.expires_at && new Date(keyRecord.expires_at) < new Date()) {
        return res.status(401).json({
            error: { code: 'API_KEY_EXPIRED', message: 'API Key 已过期' }
        });
    }
    
    req.apiKey = keyRecord;
    next();
}

9.7 认证方案对比

方案安全性可伸缩性适用场景
Basic内部工具、调试
API Key公开 API
Bearer Token (JWT)现代 Web 应用
Session + Cookie需共享存储传统 Web
OAuth 2.0最高第三方登录

9.8 业务场景:多层认证架构

// 路由级别的不同认证策略
const express = require('express');
const app = express();

// 公开路由 — 无需认证
app.get('/api/public/products', (req, res) => {
    res.json({ products: [...] });
});

// API Key 认证 — 第三方集成
app.get('/api/v1/external', apiKeyAuth, (req, res) => {
    res.json({ data: [...] });
});

// JWT 认证 — 前端应用
app.get('/api/users/me', jwtAuth, (req, res) => {
    res.json({ user: req.user });
});

// 角色授权 — 管理员
app.get('/api/admin/users', jwtAuth, requireRole('admin'), (req, res) => {
    res.json({ users: [...] });
});

⚠️ 注意事项

  1. 始终使用 HTTPS:所有认证方案都必须配合 TLS
  2. JWT 不要存敏感信息:Payload 只是 Base64 编码
  3. Token 过期:Access Token 短生命周期,配合 Refresh Token
  4. 不要在 URL 中传递 Token:会被日志记录
  5. 限制 API Key 权限:遵循最小权限原则
  6. 记录认证日志:登录、Token 刷新、认证失败都要记录

🔗 扩展阅读


下一章第 10 章:跨域资源共享 CORS — CORS 机制、预检请求、配置实践