5.1 认证、签名与工作区

5.1 认证方式

dwz-server 支持两种认证方式。内网和测试场景推荐 Bearer Token;生产外联推荐 HMAC 签名。

方式一:Bearer Token

获取 Token

POST /api/v1/auth/login
Content-Type: application/json

{
  "username": "admin",
  "password": "admin123"
}

响应:

{
  "code": 0,
  "data": {
    "token": "eyJhbGciOi...",
    "expire_at": "2026-04-16T10:00:00Z"
  }
}

使用 Token

Authorization: Bearer eyJhbGciOi...

curl 示例

TOKEN="eyJhbGciOi..."
curl -X POST http://localhost:8080/api/v1/short_links \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"original_url": "https://example.com"}'

个人访问 Token

除登录 JWT 之外,可通过 POST /api/v1/tokens 创建长期有效的 个人访问 Token,同样以 Bearer 方式使用,适合脚本集成。可通过 DELETE /api/v1/tokens/{token_id} 随时吊销。

方式二:HMAC-SHA256 签名(推荐生产外联)

核心优势:App Secret 永远不上网,即使请求被截获也无法伪造签名。

请求头

Header 说明
X-App-Id 应用标识
X-Signature HMAC-SHA256 签名(十六进制)
X-Timestamp Unix 秒级时间戳,服务端允许 ±5 分钟偏差
X-Nonce 随机字符串(16-32 字符),防重放

签名公式

signature = HMAC-SHA256(app_secret, string_to_sign)
string_to_sign = method + path + sorted_params_json + timestamp + nonce
  • method:HTTP 方法大写
  • path:请求路径,如 /api/v1/short_links
  • sorted_params_json:参数按 key 字母序排序后的 JSON
    • GET 使用 query
    • POST/PUT/PATCH 使用 body
    • 无参数用 {}
  • timestamp / nonce:与请求头一致

Python 签名实现

import hmac, hashlib, json, time, uuid, requests

APP_ID = "app_xxx"
APP_SECRET = "your_app_secret"
BASE = "http://localhost:8080"

def sign(method, path, params):
    ts = str(int(time.time()))
    nonce = uuid.uuid4().hex
    params_json = json.dumps(params, sort_keys=True, separators=(",", ":"))
    string_to_sign = f"{method}{path}{params_json}{ts}{nonce}"
    sig = hmac.new(APP_SECRET.encode(), string_to_sign.encode(),
                   hashlib.sha256).hexdigest()
    return {
        "X-App-Id": APP_ID,
        "X-Signature": sig,
        "X-Timestamp": ts,
        "X-Nonce": nonce,
    }

body = {"original_url": "https://example.com", "title": "demo"}
headers = sign("POST", "/api/v1/short_links", body)
headers["Content-Type"] = "application/json"
r = requests.post(f"{BASE}/api/v1/short_links", headers=headers, json=body)
print(r.json())

常见签名错误

  • 40101 / 签名不匹配:确认 JSON 的 key 已按字母序排序、没有多余空白字符
  • 时间戳偏差:服务器与客户端时间差超过 5 分钟会拒绝
  • nonce 重复:同一 nonce 在窗口内只能使用一次