欢迎使用全新的 WEEX API 文档。需要查看旧版本?切换到旧版文档
WEEX
经纪商

签名

背景

在 OAuth 授权平台中,获取 access_token接口为三方服务端调用授权服务器的敏感接口。需要校验 OAuth 业务参数合法性,还需要校验请求是否确实由已注册的三方客户端发起,并防止请求在传输过程中被篡改或被重复提交。 因此,在 OAuth 协议参数校验之外,引入基于 HMAC-SHA256 的客户端请求签名机制,用于实现:

  • 客户端身份校验
  • 请求完整性保护
  • 请求防重放

请求头定义

Header 名称是否必填说明
Client-Id是三方客户端标识,对应平台注册的 client_id
Sign是请求签名值
Timestamp是请求发起时间戳,单位毫秒
Nonce是一次性随机串,用于防重放

签名规则

签名各字段说明

  • timestamp:请求发起时间戳,单位毫秒,与请求头 Timestamp 相同。
  • nonce:一次性随机串,与请求头 Nonce 相同,用于防重放。
  • method:请求方法(POST/GET 等),字母全部大写。
  • requestPath:请求接口路径,例如 /v1/oauth/token。
  • queryString:请求 URL 中 ? 后的查询字符串,不存在时为空。
  • body:请求主体对应的字符串,如果请求没有主体(通常为 GET 请求)则 body 按空字符串处理。

queryString 为空时,签名格式

timestamp + nonce + method.toUpperCase() + requestPath + body

queryString 不为空时,签名格式

timestamp + nonce + method.toUpperCase() + requestPath + "?" + queryString + body

举例说明

例1:无 QueryString(获取令牌)

  • timestamp = 1710000000000
  • nonce = abc123xyz
  • method = "POST"
  • requestPath = "/v1/oauth/token"
  • body = {"grantType":"authorization_code","code":"code123","redirectUri":"https://client.example.com/callback","codeVerifier":"verifier123"}

生成待签名的字符串:

1710000000000abc123xyzPOST/v1/oauth/token{"grantType":"authorization_code","code":"code123","redirectUri":"https://client.example.com/callback","codeVerifier":"verifier123"}

生成最终签名的步骤

  1. 将待签名字符串 baseString 使用客户端密钥 clientSecret 进行 HMAC-SHA256 加密,并对加密结果进行 Base64 编码
    • Sign = Base64(HMAC-SHA256(clientSecret, baseString))

签名示例

Java

JavaCode
package com.weex.utils; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.UUID; public class OAuthApiClient { /** * 替换为实际的 clientId / clientSecret */ private static final String CLIENT_ID = "your-client-id"; private static final String CLIENT_SECRET = "your-client-secret"; /** * 替换为实际的 OAuth 授权服务器地址 */ private static final String BASE_URL = "https://auth.example.com"; /** * HMAC-SHA256 算法名称 */ private static final String HMAC_SHA256 = "HmacSHA256"; /** * 生成签名 * * 签名原文规则: * 1. 无 queryString: * timestamp + nonce + method.toUpperCase() + requestPath + body * * 2. 有 queryString: * timestamp + nonce + method.toUpperCase() + requestPath + queryString + body * * 说明: * - queryString 为空时传空字符串 "" * - queryString 非空时建议格式为 "?a=1&b=2" * - body 为空时传空字符串 "" */ public static String generateSignature(String clientSecret, String timestamp, String nonce, String method, String requestPath, String queryString, String body) throws Exception { String safeQueryString = queryString == null ? "" : queryString; String safeBody = body == null ? "" : body; String message = timestamp + nonce + method.toUpperCase() + requestPath + safeQueryString + safeBody; return hmacSha256Base64(clientSecret, message); } /** * HMAC-SHA256 后进行 Base64 编码 */ private static String hmacSha256Base64(String secretKey, String message) throws Exception { SecretKeySpec secretKeySpec = new SecretKeySpec( secretKey.getBytes(StandardCharsets.UTF_8), HMAC_SHA256 ); Mac mac = Mac.getInstance(HMAC_SHA256); mac.init(secretKeySpec); byte[] signatureBytes = mac.doFinal(message.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(signatureBytes); } /** * 生成毫秒时间戳 */ private static String generateTimestamp() { return String.valueOf(System.currentTimeMillis()); } /** * 生成随机 nonce */ private static String generateNonce() { return UUID.randomUUID().toString().replace("-", ""); } /** * 发送 POST 请求 * * 说明: * - body 必须是客户端最终实际发送的 JSON 字符串 * - 签名时使用的 body,必须与请求发送的 body 完全一致 */ public static String sendPost(String clientId, String clientSecret, String requestPath, String queryString, String body) throws Exception { String timestamp = generateTimestamp(); String nonce = generateNonce(); String signature = generateSignature( clientSecret, timestamp, nonce, "POST", requestPath, queryString, body ); String url = BASE_URL + requestPath + (queryString == null ? "" : queryString); HttpPost postRequest = new HttpPost(url); postRequest.setHeader("Client-Id", clientId); postRequest.setHeader("Sign", signature); postRequest.setHeader("Timestamp", timestamp); postRequest.setHeader("Nonce", nonce); postRequest.setHeader("Content-Type", "application/json"); StringEntity entity = new StringEntity(body, StandardCharsets.UTF_8); postRequest.setEntity(entity); try (CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = httpClient.execute(postRequest)) { int statusCode = response.getStatusLine().getStatusCode(); String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); System.out.println("HTTP Status: " + statusCode); return responseBody; } } /** * 示例调用:authorization_code 模式换取 token */ public static void main(String[] args) { try { String requestPath = "/v1/oauth/token"; /** * 注意: * 这里的 body 字符串就是最终实际发送的 JSON 字符串 * 签名时必须使用这一份 body,发送时也必须发送这一份 body */ String body = "{" + "\"grantType\":\"authorization_code\"," + "\"code\":\"auth_code_xxx\"," + "\"redirectUri\":\"https://client.example.com/callback\"," + "\"codeVerifier\":\"code_verifier_xxx\"" + "}"; String response = sendPost( CLIENT_ID, CLIENT_SECRET, requestPath, "", body ); System.out.println("Response: " + response); } catch (Exception e) { e.printStackTrace(); } } }