























签名规范
可直接复用代码(签名 + 验签)
import hmac import hashlib import json from typing import Dict, Iterable, Tuple, Union, Optional from urllib.parse import urlparse, parse_qs ArgsType = Optional[Dict[str, Union[str, Iterable[str]]]] def _compact_json_bytes(body: Optional[dict]) -> bytes: """ 将字典转为“紧凑 JSON”UTF-8字节;None/空字典→空字节 """ if not body: return b"" raw = json.dumps(body, separators=(",", ":"), ensure_ascii=False) return raw.encode("utf-8") def _sorted_query(args: ArgsType) -> str: """ 将 query 参数按 key 升序拼接为 k=v&k2=v2。多值取第一个。 None 或空返回 ""。 """ if not args: return "" keys = sorted(args.keys()) pairs = [] for k in keys: v = args[k] if isinstance(v, (list, tuple, set)): v = list(v)[0] if v else "" pairs.append(f"{k}={v}") return "&".join(pairs) def build_string_to_sign( http_method: str, uri_path: str, args: ArgsType, body_bytes: bytes, ) -> Tuple[str, str]: """ 构造待签名串与 content_md5(hex 小写) 返回: (string_to_sign, content_md5) """ method = http_method.upper() content_md5 = hashlib.md5(body_bytes).hexdigest() qs = _sorted_query(args) uri_and_qs = f"{uri_path}?{qs}" if qs else uri_path string_to_sign = f"{method}\n{content_md5}\n{uri_and_qs}" return string_to_sign, content_md5 def generate_signature( http_method: str, uri_path: str, args: ArgsType, body: Optional[dict], secret: Union[str, bytes], ) -> Tuple[str, bytes, str]: """ 生成签名 返回: (signature_hex, body_bytes, string_to_sign) """ if isinstance(secret, str): secret = secret.encode("utf-8") body_bytes = _compact_json_bytes(body) string_to_sign, _ = build_string_to_sign(http_method, uri_path, args, body_bytes) signature = hmac.new(secret, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest() return signature, body_bytes, string_to_sign def verify_signature( http_method: str, uri_path: str, args: ArgsType, raw_body_bytes: bytes, provided_signature_hex: str, secret: Union[str, bytes], ) -> bool: """ 验签。注意 raw_body_bytes 必须是请求体原始字节(未二次序列化)。 """ if isinstance(secret, str): secret = secret.encode("utf-8") string_to_sign, _ = build_string_to_sign(http_method, uri_path, args, raw_body_bytes) expected = hmac.new(secret, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest() # 常量时间比较,避免时序侧信道 return hmac.compare_digest(provided_signature_hex, expected)
客户端请求示例(Python requests,可直接复制)
import os import requests from urllib.parse import urljoin, urlparse, parse_qs # 引入上方的 generate_signature # from sign_utils import generate_signature BASE_URL = os.getenv("API_BASE_URL", "https://api.example.com") URI_PATH = "/open_api/query/template" URL = urljoin(BASE_URL, URI_PATH) APP_ID = os.getenv("MY_APP_ID", "your_app_id_here") SECRET = os.getenv("MY_SECRET", "your_secret_here") # 建议使用环境变量配置 body = {"template_id": "your_template_id"} # 解析 query(若 URL 上还有 ?k=v,可从这里带入参与签名) args = parse_qs(urlparse(URL).query) sign, body_bytes, string_to_sign = generate_signature( http_method="POST", uri_path=URI_PATH, args=args, body=body, secret=SECRET, ) headers = { "WX-SIGN": sign, "WX-APPID": APP_ID, "Content-Type": "application/json; charset=utf-8", # "WX-DEBUG": "true", # 调试时可开启 } # 以原始字节发送,确保与签名一致 resp = requests.post(URL, data=body_bytes, headers=headers, timeout=10) print("status:", resp.status_code) print("resp:", resp.text) # 调试输出 print("string_to_sign:", repr(string_to_sign))
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。