业务 API 签名机制

概述

调用盟主云业务 API 时,服务端会对请求做身份校验。请求至少需要携带 appidsign;建议同时携带 expired 控制请求有效期。签名不正确、appid 无效或已过期时,可能返回“签名校验失败”“auth failed”或参数错误。

本文适用于 api.zmengzhu.com 下的业务 API,例如:

https://api.zmengzhu.com/business/v1/user/createThirdUser

注意:/open/authorize/login 是开放授权登录跳转接口,签名和使用场景单独说明,详见开放授权登录文档。

公共参数

参数名 类型 必选 说明
appid string 商户应用 ID,由盟主平台提供
expired int 请求过期时间,10 位秒级 Unix 时间戳,建议设置为当前时间后 5 到 10 分钟
sign string 请求签名,32 位 MD5 字符串

如果携带 expired,必须大于服务端当前时间。每次修改 expired 或业务参数后,都必须重新生成 sign

请求格式

业务 API 通常使用以下格式:

POST https://api.zmengzhu.com/business/v1/user/createThirdUser?appid=10000001&expired=1999999999&sign=...
Content-Type: application/x-www-form-urlencoded

POST Body 使用表单参数,例如:

nickname=微信用户
third_uid=user-001
avatar=https://example.com/avatar.png

不建议使用 raw JSON 作为请求 body。当前业务 API 按表单参数读取 POST 数据,使用 JSON 可能导致服务端读不到参数,或导致签名字符串和实际请求不一致。

签名规则

第一步:准备 URL 参数

先准备除 sign 以外的 URL query 参数,例如:

appid=10000001&expired=1999999999

如果接口还有其他 GET 参数,也需要放在 query 中一起参与签名。最终请求时使用的 query 顺序,应和签名时保持一致。

第二步:生成 urlSuffix

urlSuffix 是去掉协议后的完整接口地址:

域名 + 路径 + ? + queryStringWithoutSign

示例:

api.zmengzhu.com/business/v1/user/createThirdUser?appid=10000001&expired=1999999999

注意:签名时不包含 http://https://

第三步:准备 POST 表单参数

POST 参数不包含 sign,按参数名升序排序,然后将 key 和 value 直接拼接。

例如 POST 参数为:

{
  "nickname": "微信用户",
  "third_uid": "user-001",
  "avatar": "https://example.com/avatar.png"
}

按 key 升序排序后为:

avatar
nickname
third_uid

拼接后的 sortString 为:

avatarhttps://example.com/avatar.pngnickname微信用户third_uiduser-001

注意:sortString 中没有 =,没有 &,POST 参数值参与签名时使用原始值,不使用 URL encode 后的值。

第四步:生成待签名字符串

urlSuffix + sortString + secret_key

示例:

api.zmengzhu.com/business/v1/user/createThirdUser?appid=10000001&expired=1999999999avatarhttps://example.com/avatar.pngnickname微信用户third_uiduser-001secret

第五步:计算 sign

对待签名字符串做 MD5:

md5(signSource)

得到:

ff3ed927e8c800ce843f38ba7d1d6f59

第六步:拼接最终请求 URL

https://api.zmengzhu.com/business/v1/user/createThirdUser?appid=10000001&expired=1999999999&sign=ff3ed927e8c800ce843f38ba7d1d6f59

完整示例

示例参数

接口:

POST https://api.zmengzhu.com/business/v1/user/createThirdUser

URL 参数:

{
  "appid": "10000001",
  "expired": 1999999999
}

POST 表单参数:

{
  "nickname": "微信用户",
  "third_uid": "user-001",
  "avatar": "https://example.com/avatar.png"
}

示例密钥:

secret

每一步执行结果

queryStringWithoutSign

appid=10000001&expired=1999999999

urlSuffix

api.zmengzhu.com/business/v1/user/createThirdUser?appid=10000001&expired=1999999999

排序后的 POST 参数:

avatar
nickname
third_uid

sortString

avatarhttps://example.com/avatar.pngnickname微信用户third_uiduser-001

signSource

api.zmengzhu.com/business/v1/user/createThirdUser?appid=10000001&expired=1999999999avatarhttps://example.com/avatar.pngnickname微信用户third_uiduser-001secret

sign

ff3ed927e8c800ce843f38ba7d1d6f59

最终请求 URL:

https://api.zmengzhu.com/business/v1/user/createThirdUser?appid=10000001&expired=1999999999&sign=ff3ed927e8c800ce843f38ba7d1d6f59

最终表单 Body:

nickname=%E5%BE%AE%E4%BF%A1%E7%94%A8%E6%88%B7&third_uid=user-001&avatar=https%3A%2F%2Fexample.com%2Favatar.png

PHP 示例

<?php

$appId = '10000001';
$secretKey = 'secret';

$host = 'api.zmengzhu.com';
$path = '/business/v1/user/createThirdUser';
$expired = 1999999999;

$postData = [
    'nickname' => '微信用户',
    'third_uid' => 'user-001',
    'avatar' => 'https://example.com/avatar.png',
];

// URL 参数不包含 sign。
$queryStringWithoutSign = 'appid=' . $appId . '&expired=' . $expired;
$urlSuffix = $host . $path . '?' . $queryStringWithoutSign;

// POST 参数按 key 升序排序后拼接 key + value。
$signPostData = $postData;
ksort($signPostData);

$sortString = '';
foreach ($signPostData as $key => $value) {
    $sortString .= $key . $value;
}

$signSource = $urlSuffix . $sortString . $secretKey;
$sign = md5($signSource);

$url = 'https://' . $urlSuffix . '&sign=' . $sign;
$formBody = http_build_query($postData);

echo "urlSuffix:\n" . $urlSuffix . "\n\n";
echo "sortString:\n" . $sortString . "\n\n";
echo "signSource:\n" . $signSource . "\n\n";
echo "sign:\n" . $sign . "\n\n";
echo "url:\n" . $url . "\n\n";
echo "formBody:\n" . $formBody . "\n";

输出结果:

urlSuffix:
api.zmengzhu.com/business/v1/user/createThirdUser?appid=10000001&expired=1999999999

sortString:
avatarhttps://example.com/avatar.pngnickname微信用户third_uiduser-001

signSource:
api.zmengzhu.com/business/v1/user/createThirdUser?appid=10000001&expired=1999999999avatarhttps://example.com/avatar.pngnickname微信用户third_uiduser-001secret

sign:
ff3ed927e8c800ce843f38ba7d1d6f59

url:
https://api.zmengzhu.com/business/v1/user/createThirdUser?appid=10000001&expired=1999999999&sign=ff3ed927e8c800ce843f38ba7d1d6f59

formBody:
nickname=%E5%BE%AE%E4%BF%A1%E7%94%A8%E6%88%B7&third_uid=user-001&avatar=https%3A%2F%2Fexample.com%2Favatar.png

curl 示例

curl -i -X POST 'https://api.zmengzhu.com/business/v1/user/createThirdUser?appid=10000001&expired=1999999999&sign=ff3ed927e8c800ce843f38ba7d1d6f59' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'nickname=微信用户' \
  --data-urlencode 'third_uid=user-001' \
  --data-urlencode 'avatar=https://example.com/avatar.png'

createThirdUser 参数说明

创建免授权用户接口:

POST https://api.zmengzhu.com/business/v1/user/createThirdUser

URL 公共参数:

参数名 类型 必选 说明
appid string 商户应用 ID
expired int 请求过期时间,10 位秒级 Unix 时间戳,建议传入
sign string 请求签名

POST 表单参数:

参数名 类型 必选 说明
third_uid string 第三方平台用户唯一标识
nickname string 第三方用户昵称,最长 16 个字符
avatar string 第三方用户头像 URL

成功返回示例:

{
  "code": 200,
  "msg": "ok",
  "data": {
    "uid": 12345678,
    "atom": "xxxxxx"
  }
}

注意:昵称字段名是 nickname,不是 name

常见问题

sign 是否参与签名

不参与。先用不含 sign 的 URL 和 POST 参数生成签名,再把 sign 追加到最终请求 URL。

签名时是否包含协议

不包含。签名从域名开始:

api.zmengzhu.com/business/v1/user/createThirdUser?...

最终发起 HTTP 请求时才加上 https://

POST 参数是否需要 URL encode 后参与签名

不需要。POST 参数参与签名时使用原始值,例如:

avatarhttps://example.com/avatar.png

真正发送请求时,表单 body 可以按 application/x-www-form-urlencoded 规则编码。

POST 参数顺序是否重要

发送请求时顺序不重要,但签名时必须按参数 key 升序排序后拼接。

可以使用 JSON body 吗

不建议。请使用:

Content-Type: application/x-www-form-urlencoded

expired 如何生成

expired 是 10 位秒级时间戳,建议设置为:

expired = 当前秒级时间戳 + 600

如果携带 expired,它必须大于服务端当前时间。expired 改变后,sign 必须重新生成。

返回 auth failed 怎么排查

auth failed 通常表示签名校验未通过或 appid 未通过认证。建议按以下顺序排查:

  1. appidsecret_key 是否属于同一个商户应用。
  2. expired 是否已经过期。
  3. 签名使用的 URL query 是否和最终请求 URL 完全一致。
  4. 是否把 sign 也放进了待签名字符串。
  5. POST 参数是否按 key 升序排序后参与签名。
  6. 请求 Body 是否使用 application/x-www-form-urlencoded
  7. nickname 字段是否误写成 name