服务端回调验签机制
前言
为了保证数据安全,我们提供了签名机制。虽然防火墙白名单起到了一定的作用,并且不对内容验签功能也能完成,但是我们还强烈建议游戏对回调内容验签以避免安全漏洞。
验签过程
从请求中获取验签字段
字段 | 说明 | 示例 |
---|---|---|
HTTP 请求方法(Method) | 获取 HTTP 请求的方法(GET、POST、PUT)等 | POST |
Path | 获取请求的绝对 URL,并去除域名部分得到参与签名的 URL。如果请求中有查询参数,请去除请求参数只保留请求路径 | /test/v1/callback/receive |
应答时间戳(Timestamp) | HTTP 头 Timestamp 中的应答时间戳 | 1642646059 |
应答随机(Nonce) | HTTP 头 Nonce 中的应答随机字符串 | 7b872f48-5a86-4665-8d1c-da3827698ec9 |
应答报文主体(body) | response Body 需要按照接口返回的顺序进行验签(字段顺序不能变),GET 请求为空 | {"trxNo":313624737144475650,"status":2} |
备注
response Body 需要按照接口返回的顺序进行验签,字段顺序不能变。
构造验签串
按照以下规则构造应答的验签名串。签名串共有三行,行尾以 \n
结束,包括最后一行。\n
为换行符(ASCII 编码值为 0x0A
)。若应答报文主体为空(如 HTTP 状态码为 204 No Content),最后一行仅为一个 \n
换行符。
HTTP 请求方法 \n
URL\n
应答时间戳 \n
应答随机串 \n
应答报文主体 \n
假设游戏服务端提供的支付结果回调地址为 : https://gameserver.xd.com/test/v1/callback/receive
,游戏服务端在接受到回调时的 HTTP 报文为:
HTTP/1.1 200 OK
Server: nginx
Date: Tue, 02 Apr 2019 12:59:40 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 2204
Connection: keep-alive
Keep-Alive: timeout=8
Content-Language: zh-CN
Request-ID: e2762b10-b6b9-5108-a42c-16fe2422fc8a
Nonce: 7b872f48-5a86-4665-8d1c-da3827698ec9
Signature: UmwMNlOA3+/MsMm5GaTog5PR8R657VVtozKw6WMSPIEkzzYUIzj0Kz+UR3VMg2ga2kQcnFIcvnlr96jTeHpTy8ixD2UINdmQOovfMAmk8SYG9Lyyed+IHkxL/zJ8JDgK6+5VrlhicAIAEyOcqbWItFNQDeH/fq0erVD+pvVPJJi4SQRNLzFGZvueTee7l2q684eupM4vUK/6Zivp5QhCW9dBX28FlQ3v5WWv8eKrzzudmxxBnQiKY4hu0pWTUAcsixlJWNKdOfRKpU6COMVW8cRa1X9cTQsL9gm1peOpVB0XTRtGilc6pWuCPSJOiDjYpIaSFOE9pEFILGjZts/SaQ==
Timestamp: 1642646059
Cache-Control: no-cache, must-revalidate
{"bankTrxNo":"","trxNo":313624737144475648,"notifyTime":1642646059424,"channel":1,"trxType":0,"userId":"313624253256011776","outTrxNo":"897298475","platform":1,"paymentType":0,"products":[{"productCode":"global.recharge.coin2.99","quantity":1}],"totalAmount":30.000,"appId":1111,"currency":"USD","notifyId":313625136534491136,"attach":{"gameServerId":"999","gameExt":"","gameRoleId":"5559981"},"status":2}
则验签字符串(称为:signBase
)为:
POST
/test/v1/callback/receive
1642646059
7b872f48-5a86-4665-8d1c-da3827698ec9
{"bankTrxNo":"","trxNo":313624737144475648,"notifyTime":1642646059424,"channel":1,"trxType":0,"userId":"313624253256011776","outTrxNo":"897298475","platform":1,"paymentType":0,"products":[{"productCode":"global.recharge.coin2.99","quantity":1}],"totalAmount":30.000,"appId":1111,"currency":"USD","notifyId":313625136534491136,"attach":{"gameServerId":"999","gameExt":"","gameRoleId":"5559981"},"status":2}
获取签名 Signature
游戏服务端通过 HTTP 头里 Signature 字段获取到平台经过加密的签名(称为 :Signature
)。对 Signature 的字段值使用 Base64 进行解码,得到签名(称为 :sign
)。
使用公钥验签
使用平台公钥对验签名串(signBase)和签名(sign)进行 SHA256 with RSA 签名验证。
信息
平台公钥在开通支付回调或者账户回调时平台会提供给游戏
命令行验签示例
下面展示使用命令行简单演示如何进行验签 :
- 首先,获取平台公钥
cat cert.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4mpfpysBNNe43XXANDpT
UD4itQ/9HjhaqW2uwufwKm9YFoYOQ7c80N5J6J7gpswYHiFsn0uE/f3ybNwhGczJ
ayPM/i8Jcbiak/28Q62s+xg26Ju2WI1/CD/xdxSJEpnPiSPUv5az1SUIlu0/2b7U
1N0j+VqaS+T4odnkvrkoVnK25ejQkNapzlQXuBHlXjnn0RmevfoKwKazxUkua1A8
gPsRFM1PrARgpIB5LmiimjLQqXmYulhB236ZSQMB6Yj3VBtt/6zOwYNe1fr7ug7S
3GkZGywDzzaz8bEQvr6VhleXGZAvN4FJIRJN1ypcyXgLR8ofMMYVwCiBKoLJ0IZI
7wIDAQAB
-----END PUBLIC KEY-----
- 然后把签名 base64 解码后保存为文件 signature.txt
openssl base64 -d -A <<< 'UmwMNlOA3+/MsMm5GaTog5PR8R657VVtozKw6WMSPIEkzzYUIzj0Kz+UR3VMg2ga2kQcnFIcvnlr96jTeHpTy8ixD2UINdmQOovfMAmk8SYG9Lyyed+IHkxL/zJ8JDgK6+5VrlhicAIAEyOcqbWItFNQDeH/fq0erVD+pvVPJJi4SQRNLzFGZvueTee7l2q684eupM4vUK/6Zivp5QhCW9dBX28FlQ3v5WWv8eKrzzudmxxBnQiKY4hu0pWTUAcsixlJWNKdOfRKpU6COMVW8cRa1X9cTQsL9gm1peOpVB0XTRtGilc6pWuCPSJOiDjYpIaSFOE9pEFILGjZts/SaQ==' > signature.txt
- 最后,验证签名
openssl dgst -sha256 -verify cert.pem -signature signature.txt << EOF
POST
/test/v1/callback/receive
1642646059
7b872f48-5a86-4665-8d1c-da3827698ec9
{"bankTrxNo":"","trxNo":313624737144475648,"notifyTime":1642646059424,"channel":1,"trxType":0,"userId":"313624253256011776","outTrxNo":"897298475","platform":1,"paymentType":0,"products":[{"productCode":"global.recharge.coin2.99","quantity":1}],"totalAmount":30.000,"appId":1111,"currency":"USD","notifyId":313625136534491136,"attach":{"gameServerId":"999","gameExt":"","gameRoleId":"5559981"},"status":2}
EOF
Verified OK
如果是 Get 请求,请注意 Get 请求的 body 为空,并且不带请求参数,例如:
- 首先,获取平台公钥
cat cert.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkC4PyYuf1JBTL29zCoQ7amElS0hNcW1J
qttK8b4fCjQxLKdzKBDNKJW09Iadd1CiWBj2DEd1bQbWz0ukF+8jSbwLL5BUuy/Wpacu3aRhEKd0
aUzUSmkHtmEfBSuM3AQ7oCs0QBhlk9AdBXIEC3NY87ch2rPCjInVHzRj6kyATya7nfkamZCpiFzM
wxZEnkHn7r3Jh87s8X5UviudYvdHpNvnTQ5MmvBv09FogjgnPTdP6MzW1fcclIeTAzhIaYhhXKMZ
9ylqr29Q8XBPgNZqFOhNkFPADXzSiJNUbclLTAJQTmzF4hsLfOE553LKVSapU0M8jE2tHLAALW2o
hNh9awIDAQAB
-----END PUBLIC KEY-----
- 然后把签名 base64 解码后保存为文件 signature.txt
openssl base64 -d -A <<< 'W0UQ9arPHlPvPwYYg+XI8+RcvotsW933nJVwoEMwF1OuQXpBv9Zu+Eg9H0RJQBuV7BKXwJIGTmkeVCM3KYKpFB7e4Tl+zQ2n7mUFjF2TpIj32LXK7BzbQhFFro8NZrb5IQN5Xs5SGyDqUnrlXCLPLNM9pOo617cwDOBihC8rqNWSeVDaUWzsgJzn69o64kqQYxuHP4tqbuVZSZdTKe+q5oRiqqK9f+NphYWK7FuQhSIQBFPyZc//+jP9mxypTP+TdKl7pEVlUxytekhLtDJGewZG7o8Lnvea3IZAUUqIZrlisz8Oi6l+84nNBJIwEMDSHXiMQDGif0NgZfnhGu/iNA==' > signature.txt
- 最后,验证签名
openssl dgst -sha256 -verify cert.pem -signature signature.txt << EOF
GET
/test/v1/game/role
1663747778
2439c7f9-c355-4c65-9d87-eb1de9bd8616
EOF
Verified OK
FAQ
Q: 支付回调和账户回调的验签机制是否一致?
A: 是一致的。为了避免多次开发,支付结果的回调和账户注销的回调通知验签机制保持一致。如果游戏已经接入过账户注销的回调通知,那账户和支付的验签公钥是一样的。如果没有则联系平台研发提供。
Q: 为什么验签始终对不上?
A:建议从如下几步骤排查:
- 检查从 HTTP 请求中获取的验签字段是否正确,比如 body 字段的字符串顺序不能变 。
- 排查代码是否有问题(该算法已经在多个游戏上验证过,理论上不存在问题)。
- 联系平台研发一起排查。