一、为什么需要接口鉴权
接口一旦对外开放,如果没有鉴权,谁都可以调,带来的不仅是越权访问,还有身份冒充 、参数被篡改 、同一请求被重放 等问题。所以接口鉴权本质上要回答三件事:谁在调 、请求有没有被改过 、是不是旧请求重放 。本文先帮你把「鉴权」和「传输保护」区分开,再给一张总览表,然后按基于签名的鉴权 、基于凭证与标准的鉴权、**传输保护(加解密)**三条线往下讲,顺带覆盖 Web 前后端、车机端云、云对云、A2A 等场景下怎么选型。
二、鉴权与传输安全:如何区分与总览
先把两个概念捋清楚,后面看各种手段就不会混:
- 鉴权:解决「谁在调用」------ 靠签名、AK/SK、API Key、JWT、OAuth 2.0、Basic 等手段做身份与请求合法性校验。
- 传输保护:解决「内容不被窃听或篡改」------ 通常和鉴权一起用,用对称/非对称加解密或 HTTPS 保证传输安全。
下面这张表按「鉴权 vs 传输保护」、典型场景和落地产品做了总览,方便你按场景对号入座。
| 类型 | 手段 | 典型场景 | 落地产品/场景示例 | 复杂度 | 安全性 |
|---|---|---|---|---|---|
| 鉴权 | API Key | 内部/开放平台简单鉴权 | Stripe、SendGrid、多数 SaaS 控制台 API | 低 | 中 |
| 鉴权 | 签名(如 HMAC) | 开放 API、防篡改 | 微信支付/支付宝开放平台、部分银行/支付回调 | 中 | 高 |
| 鉴权 | AK/SK | 云厂商 API | 阿里云、AWS、腾讯云、华为云、七牛对象存储 | 中 | 高 |
| 鉴权 | JWT | 无状态会话、微服务 | 前后端分离登录态、Auth0、Spring Security、很多 B 端后台 | 中 | 中高 |
| 鉴权 | OAuth 2.0 | 第三方授权、开放平台 | 微信/QQ/微博登录、GitHub 授权、企业微信/飞书开放平台 | 高 | 高 |
| 鉴权 | Basic / Digest | 传统 HTTP 认证 | 内网监控/运维接口、部分老旧企业系统、路由器/摄像头管理 | 低 | 低/中 |
| 传输保护 | 对称/非对称加解密 | 敏感字段或报文加密 | 银企直连、支付报文、部分政务/医疗接口 | 中高 | 高 |
可以简单记:鉴权管「谁在调」,传输保护管「传得安不安全」;两者可以组合用,比如先鉴权再对 body 做加密。
传输保护
加解密/HTTPS
鉴权
签名/HMAC
AK_SK
API Key
JWT
OAuth2
Basic
API 安全
三、基于签名的鉴权
这里讲两种:通用 HMAC 签名 (自建 API 或开放平台很常见)和 AK/SK(云厂商那套规范请求 + 签名)。本质都是「拿密钥对请求做签名、服务端用同一规则验签」,差别主要在于:请求要不要按云厂商的规范拼成「规范请求串」。
3.1 签名(HMAC)
做法是:把请求参数(一般会带上 timestamp、nonce)按约定规则排序、拼接成字符串,用 Secret 做 HMAC-SHA256(或 MD5)得到签名,请求时把签名一起带上;服务端用同一个 Secret 按同样规则算一遍,和传上来的签名比对,一致就通过。timestamp 还可以用来做简单的防重放(例如只接受 5 分钟内的请求)。
流程概览:
服务端 客户端 服务端 客户端 构造参数并排序拼接 HMAC-SHA256(Secret, 参数字符串) 请求 + Header 中带 signature/timestamp 用同一 Secret 重算签名 比对签名并校验时间 200 或 401
Python 示例(仅用标准库,无需 pip install):
python
# ========== HMAC 签名鉴权:客户端生成签名 + 服务端验签 ==========
# 依赖:标准库 hmac、hashlib,无需安装任何包
import hmac
import hashlib
import secrets
import time
from urllib.parse import urlencode
# 与服务端约定好的密钥,双方各持一份,绝不通过请求传递
# 生成方式(生产环境生成一次后持久化,客户端与服务端各存一份):
SECRET = secrets.token_bytes(32) # 32 字节,HMAC-SHA256 足够
# 若需可读的字符串形式(如放配置):SECRET = secrets.token_hex(32).encode()
def make_signature(params: dict) -> str:
"""
生成签名:参数按 key 排序 → 拼成 key1=val1&key2=val2 → 对该串做 HMAC-SHA256。
注意:签名时不要包含 signature 自身,否则服务端无法复现。
"""
sorted_params = sorted(params.items())
sign_str = urlencode(sorted_params)
sig = hmac.new(SECRET, sign_str.encode(), hashlib.sha256).hexdigest()
return sig
# ---------- 客户端:构造带签名的请求 ----------
# 业务参数 + 防重放:timestamp 与 nonce(一次性随机串)
params = {
"name": "test",
"timestamp": str(int(time.time())), # 服务端可据此拒绝过期请求
"nonce": secrets.token_hex(8), # 每次请求不同,防重放
}
params["signature"] = make_signature(params)
# 实际发请求时:params 可放在 query、body,或把 signature/timestamp 单独放 Header
print("客户端签名结果:", params["signature"])
# ---------- 服务端:验签 ----------
def verify_signature(params: dict) -> bool:
"""从参数中取出客户端传来的签名,用剩余参数重算,一致则通过。"""
received = params.pop("signature", None)
if not received:
return False
expected = make_signature(params)
# 用 compare_digest 避免时序攻击,不要用 ==
return hmac.compare_digest(received, expected)
# 模拟服务端收到的参数(nonce 需与客户端一致)
params_for_verify = {
"name": "test",
"timestamp": params["timestamp"],
"nonce": params["nonce"],
"signature": params["signature"],
}
print("验签通过:", verify_signature(params_for_verify))
3.2 AK/SK(Access Key / Secret Key)
和上面「签名」的思路一样,都是拿密钥对请求做 HMAC;区别在于云厂商会把 Method、URI、Query、Header、Body 等按固定规则拼成「规范请求串」,再据此生成「签名字符串」,最后用 Secret Key 算出签名,塞进 Authorization(一般带 AccessKey、SignedHeaders、Signature)。阿里云、AWS、腾讯云等开放 API 都是这一套。
流程概览:
Method+URI
规范请求串
签名字符串
HMAC-SK 得 Signature
Authorization Header
Python 示例(标准库即可):
python
# ========== AK/SK 鉴权:规范请求 + HMAC 签名 ==========
# 依赖:标准库 hmac、hashlib,无需安装
# 以下为简化版,真实对接阿里云/AWS 时请按官方文档拼规范请求(含 Header 排序等)
import hmac
import hashlib
import secrets
# 真实对接云厂商时从控制台获取;若自建类似机制可这样生成并入库下发:
ACCESS_KEY = "AK" + secrets.token_hex(16) # 例如 AKa1b2c3d...
SECRET_KEY = secrets.token_urlsafe(32) # SecretKey 仅本地/服务端持有,不可泄露
def canonical_request(method: str, path: str, query: str, body: str) -> str:
"""
拼出「规范请求串」,云厂商会严格规定格式(如每行顺序、Header 列表等)。
这里简化成:Method + Path + Query + Body 的 SHA256,实际需对照文档。
"""
body_hash = hashlib.sha256((body or "").encode()).hexdigest()
return f"{method}\n{path}\n{query}\n{body_hash}"
def sign(secret_key: str, string_to_sign: str) -> str:
"""对「签名字符串」做 HMAC-SHA256,得到最终签名。"""
return hmac.new(
secret_key.encode(), string_to_sign.encode(), hashlib.sha256
).hexdigest()
# ---------- 客户端:构造 Authorization ----------
method, path, query, body = "GET", "/api/resource", "a=1&b=2", ""
canon = canonical_request(method, path, query, body)
signature = sign(SECRET_KEY, canon)
# 常见格式:AK <AccessKey>:<Signature>,具体以云厂商文档为准
auth_header = f"AK {ACCESS_KEY}:{signature}"
print("Authorization:", auth_header)
# 服务端:用同一 SECRET_KEY,对收到的请求按相同规则拼规范串、重算 signature 并比对
四、基于凭证与标准的鉴权
这类方式不靠「对请求体做 HMAC」,而是用事先发下去的凭证 (API Key、JWT)或标准协议(OAuth 2.0、Basic Auth)来做身份和权限校验,常见于前后端分离、第三方登录、服务调服务等场景。
4.1 API Key
在 Header(如 X-API-Key)或 Query 里带一个固定 Key,服务端在库或配置里查一下是否存在、是否有效即可。实现简单,适合内部或开放平台的低敏感接口。
同步请求示例(pip install requests):
python
# ========== API Key 鉴权:客户端在 Header 中携带 Key ==========
# pip install requests
import requests
import secrets
# 服务端签发时生成并入库,客户端拿到的即此值;示例里直接生成一个
API_KEY = "sk-" + secrets.token_urlsafe(24) # 例如 sk-AbCdEf123...
# 请求时把 Key 放在 Header,服务端从 request.headers.get("X-API-Key") 取出并查库校验
resp = requests.get(
"https://api.example.com/data",
headers={"X-API-Key": API_KEY},
)
# resp.raise_for_status()
# data = resp.json()
异步请求示例(适合高并发、异步框架内调用,pip install httpx):
python
# ========== 异步调用带 API Key 的接口 ==========
# pip install httpx
import httpx
async def fetch_with_api_key(url: str, api_key: str) -> dict:
"""在异步场景下用 httpx 发请求,不阻塞事件循环。"""
async with httpx.AsyncClient() as client:
resp = await client.get(url, headers={"X-API-Key": api_key})
resp.raise_for_status()
return resp.json()
# 使用:result = await fetch_with_api_key("https://api.example.com/data", "sk-xxx")
服务端:从 request.headers.get("X-API-Key") 取出 key,在数据库或配置中查是否存在、是否过期或禁用。
4.2 JWT(JSON Web Token)
无状态:登录成功后服务端签发一个 Token(由 Header.Payload.Signature 三部分组成),客户端之后每次请求在 Authorization 里带上;服务端用同一 Secret(或公钥)验签并解析 Payload 里的用户 ID、过期时间等,无需查库即可完成鉴权。
Header
.
Payload
.
Signature
python
# ========== JWT 签发与校验 ==========
# pip install pyjwt
import jwt
import secrets
import time
# HS256 建议至少 256 位密钥;生成一次后持久化,服务端与签发/验签方共用
SECRET = secrets.token_hex(32) # 32 字节 = 256 位,hex 后为 64 字符
# 若已有固定字符串:SECRET = "your-256-bit-secret-string-at-least-32-chars"
# ---------- 服务端:登录成功后签发 Token ----------
# sub:主体(通常为用户 ID);exp:过期时间(建议设短一点,配合 refresh token)
payload = {
"sub": "user123",
"exp": int(time.time()) + 3600, # 1 小时后过期
}
token = jwt.encode(payload, SECRET, algorithm="HS256")
print("签发 Token:", token)
# ---------- 服务端:收到请求后验签并解析 ----------
# decode 会同时校验签名和 exp,过期或篡改会抛异常
decoded = jwt.decode(token, SECRET, algorithms=["HS256"])
print("解析出的 Payload:", decoded) # 含 sub、exp 等
4.3 OAuth 2.0(客户端凭证示例)
「客户端凭证」模式适合服务调服务 、无用户参与:用 client_id + client_secret 向授权服务器换一张 access_token,之后访问资源接口时在 Authorization 里带 Bearer <token> 即可。
资源接口 授权服务 客户端 资源接口 授权服务 客户端 client_id + client_secret access_token Authorization: Bearer access_token 资源
同步获取 Token 并调用资源接口(pip install requests):
python
# ========== OAuth 2.0 客户端凭证:先换 Token,再调接口 ==========
# pip install requests
import requests
def get_token(token_url: str, client_id: str, client_secret: str) -> str:
"""向授权服务器用 client 身份换 access_token。"""
r = requests.post(
token_url,
data={
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret,
},
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
r.raise_for_status()
return r.json()["access_token"]
# 使用示例:
# token = get_token("https://auth.example.com/oauth/token", "cid", "csecret")
# resp = requests.get("https://api.example.com/resource", headers={"Authorization": f"Bearer {token}"})
异步获取 Token 并调用(pip install httpx):
python
# ========== 异步 OAuth 2.0 客户端凭证 ==========
# pip install httpx
import httpx
async def get_token_async(token_url: str, client_id: str, client_secret: str) -> str:
"""异步请求 token 端点,适合在 asyncio / FastAPI 等异步环境中使用。"""
async with httpx.AsyncClient() as client:
r = await client.post(
token_url,
data={
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret,
},
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
r.raise_for_status()
return r.json()["access_token"]
# 使用:token = await get_token_async(...)
# 再:async with httpx.AsyncClient() as c: r = await c.get(resource_url, headers={"Authorization": f"Bearer {token}"})
4.4 Basic Auth
把 username:password 做 Base64 编码,放到 Authorization: Basic <base64> 里。实现简单,但密码每次请求都会带上去,必须配合 HTTPS 使用。
python
# ========== Basic Auth:用户名密码 Base64 后放 Header ==========
# 仅用标准库 base64,无需安装
import base64
def basic_header(username: str, password: str) -> str:
"""拼出 Authorization: Basic xxx,注意仅编码不加密,务必走 HTTPS。"""
raw = f"{username}:{password}"
b64 = base64.b64encode(raw.encode()).decode()
return f"Basic {b64}"
# 使用方式一:手动带 Header
# requests.get(url, headers={"Authorization": basic_header("user", "pass")})
# 使用方式二:requests 自带的 auth 参数会自动做 Basic
# requests.get(url, auth=("user", "pass"))
五、传输保护:加密与解密
鉴权只管「谁在调」;真要防窃听、防篡改,还要靠传输保护 。对请求/响应体或敏感字段做加密时,常用对称加密(AES)或非对称加密(RSA),或者两者结合:用 RSA 加密一个随机的 AES 密钥,再用 AES 加密正文(混合加密),和前面任意一种鉴权方式搭配即可。
对称加密(AES)流程:
明文
AES 加密
密文
传输
AES 解密
明文
Python 示例(pip install cryptography):
python
# ========== 对称加密 AES + 非对称 RSA(混合加密思路) ==========
# pip install cryptography
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.asymmetric import rsa, padding as rsa_padding
from cryptography.hazmat.primitives import hashes
import os
# ---------- AES 对称加解密:同一密钥加解密,适合对 body 或字段加密 ----------
def aes_encrypt(key: bytes, plaintext: str) -> bytes:
"""CBC 模式需要 IV,这里把 IV 拼在密文前 16 字节,解密时先取出再解。"""
iv = os.urandom(16)
padder = padding.PKCS7(128).padder()
padded = padder.update(plaintext.encode()) + padder.finalize()
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
enc = cipher.encryptor().update(padded) + cipher.encryptor().finalize()
return iv + enc # 实际传参时可 base64 编码后放 body
def aes_decrypt(key: bytes, ciphertext: bytes) -> str:
"""前 16 字节为 IV,后面为密文;解密后去掉 PKCS7 padding。"""
iv, enc = ciphertext[:16], ciphertext[16:]
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
dec = cipher.decryptor().update(enc) + cipher.decryptor().finalize()
unpadder = padding.PKCS7(128).unpadder()
return (unpadder.update(dec) + unpadder.finalize()).decode()
# 示例:32 字节 key 对应 AES-256;生产里可从密钥管理服务获取或由 KDF 派生
key = os.urandom(32) # 或 secrets.token_bytes(32)
ct = aes_encrypt(key, "hello api")
print("解密结果:", aes_decrypt(key, ct))
# ---------- RSA:公钥加密、私钥解密,常用于加密「对称密钥」或做签名 ----------
private_key = rsa.generate_private_key(
public_exponent=65537, key_size=2048, backend=default_backend()
)
public_key = private_key.public_key()
def rsa_encrypt(pub_key, data: bytes) -> bytes:
"""用对方公钥加密,仅对方私钥可解密;数据不宜过长,适合加密 AES 的 key。"""
return pub_key.encrypt(
data, rsa_padding.OAEP(rsa_padding.MGF1(hashes.SHA256()), hashes.SHA256(), None)
)
# 混合加密常见用法:生成随机 AES key → 用 RSA 公钥加密该 key → 用 AES 加密 body
# 请求里带:encrypted_key + ciphertext,对方用私钥解出 key 再解 body
六、小结与选型建议
按场景选方式
| 场景 | 建议方式 |
|---|---|
| 内部/简单对接 | API Key 或简单签名 |
| 开放 API、防篡改 | 签名(HMAC)或 AK/SK |
| 需加密传输 | 在鉴权之上加 AES/RSA 或混合加密 |
| 无状态、多服务 | JWT |
| 第三方授权、开放平台 | OAuth 2.0 |
按架构/部署形态选型(Web 前后端、车机端云、云对云、A2A)
| 架构场景 | 说明 | 常用鉴权与传输方式 | 注意点 |
|---|---|---|---|
| Web 前后端 | 浏览器/SPA/小程序 ↔ 后端服务 | JWT、Session + Cookie、OAuth 2.0 授权码 | Token 存放与刷新、CORS、HTTPS;避免敏感密钥暴露在前端 |
| 车机端云 | 车机/T-Box/IVI ↔ 车云平台 | 设备证书 + 签名、AK/SK、或厂商自定义签名协议 | 弱网与断网续传、设备身份绑定、OTA 与密钥轮换 |
| 云对云 API | 某云/自建服务 ↔ 另一云或第三方 API | AK/SK、OAuth 2.0 客户端凭证、mTLS、API Key | 服务账号与权限最小化、审计与调用限流、密钥轮换与保管 |
| A2A(应用对应用) | 服务 ↔ 服务、API ↔ API,无用户参与 | 同云对云:AK/SK、Client Credentials、API Key、签名 | 机器身份、调用链与审计、idempotency 与重试策略 |
选型时可以先看「按场景」或「按架构」表,再回到前文第三节(签名)、第四节(凭证)、第五节(加解密)对号入座;需要高并发、异步框架里调接口时,可优先用 httpx 的异步写法。