DPoP(Demonstrating Proof-of-Possession)是 RFC 9449 定义的标准协议,用于将请求绑定到特定客户端的密钥对上。Mercari API 使用 DPoP 作为请求鉴权的一部分。
DPoP Token 本质是一个 JWT(JSON Web Token),格式为 header.payload.signature,三段 Base64URL 编码。
{
"typ": "dpop+jwt",
"alg": "ES256",
"jwk": {
"crv": "P-256",
"kty": "EC",
"x": "j9I6kLKekdSNdHyHsaZl5gkbbFhDaAP3DwsuvVjCrWg",
"y": "NLtDkddYVafFykQGbk-x6AaJzAjUnVepYt_jsvewpgI"
}
}
| 字段 | 值 | 说明 |
|---|---|---|
typ |
dpop+jwt |
标识为 DPoP 类型的 JWT |
alg |
ES256 |
签名算法:ECDSA + P-256 曲线 + SHA-256 |
jwk |
EC 公钥 | 内嵌椭圆曲线公钥,服务端用来验证签名 |
LIST_DPOP(搜索列表接口):
{
"iat": 1778046219,
"jti": "44bc836e-abaa-4258-a248-3e691153f665",
"htu": "https://api.mercari.jp/v2/entities:search",
"htm": "POST",
"uuid": "a00429c5-ad26-4be4-83ae-60b7239e14d5"
}
DETAIL_DPOP(商品详情接口):
{
"iat": 1778046009,
"jti": "1f4f049a-7f0f-4c4f-af71-20baad8a1784",
"htu": "https://api.mercari.jp/items/get",
"htm": "GET",
"uuid": "a00429c5-ad26-4be4-83ae-60b7239e14d5"
}
| 字段 | 说明 | LIST 值 | DETAIL 值 |
|---|---|---|---|
iat |
签发时间(Unix 时间戳) | 1778046219 |
1778046009 |
jti |
JWT ID(UUID v4 随机值,防重放) | 44bc836e-... |
1f4f049a-... |
htu |
HTTP Target URI(目标接口地址) | https://api.mercari.jp/v2/entities:search |
https://api.mercari.jp/items/get |
htm |
HTTP Method(请求方法) | POST |
GET |
uuid |
设备标识(与 LAPLACE_DEVICE_UUID 相同) |
a00429c5-... |
a00429c5-... |
使用 EC 私钥对 header_base64url.payload_base64url 进行 ES256 签名,输出 64 字节的 raw r||s 格式(各 32 字节),再做 Base64URL 编码。
iat(issued at)字段是一个固定的时间戳。服务端会校验该时间与当前时间的差值,超过有效期窗口(通常几天)就会拒绝请求。硬编码的 token 中 iat 永远不变,所以过几天就失效。
DPoP 规范要求 token 绑定到具体的接口和方法:
| 差异字段 | LIST(搜索列表) | DETAIL(商品详情) |
|---|---|---|
htu |
/v2/entities:search |
/items/get |
htm |
POST |
GET |
jti |
各自独立的随机 UUID | 各自独立的随机 UUID |
iat |
各自的签发时间 | 各自的签发时间 |
服务端会验证 htu 和 htm 是否匹配实际请求,所以两个接口不能共用同一个 DPoP token。
1. 生成 EC P-256 密钥对(私钥 + 公钥)
↓
2. 构造 JWT Header(typ + alg + 公钥 jwk)
↓
3. 构造 JWT Payload(iat=当前时间戳, jti=随机UUID, htu=目标URL, htm=方法, uuid=设备ID)
↓
4. 拼接 base64url(header).base64url(payload)
↓
5. 用私钥对拼接字符串做 ES256 签名
↓
6. 输出 base64url(header).base64url(payload).base64url(signature)
已封装在 dpop_generator.py 中,依赖 cryptography 库。
from dpop_generator import generate_list_dpop, generate_detail_dpop
# 搜索列表接口
list_dpop = generate_list_dpop()
# 商品详情接口
detail_dpop = generate_detail_dpop()
from dpop_generator import DPoPGenerator
gen = DPoPGenerator(device_uuid="your-device-uuid")
list_dpop = gen.generate(htu="https://api.mercari.jp/v2/entities:search", htm="POST")
detail_dpop = gen.generate(htu="https://api.mercari.jp/items/get", htm="GET")
将 mercari_jp_spider.py 中的硬编码常量替换为动态调用:
from dpop_generator import generate_list_dpop, generate_detail_dpop
# build_headers() 中:
"dpop": generate_list_dpop(),
# get_detail_page() 中:
"dpop": generate_detail_dpop(),
每次调用自动生成新的 iat 和 jti,不会再过期。同一个 DPoPGenerator 实例复用同一密钥对,保证签名一致性。
pip install cryptography