# 签名生成

> 来源：https://help.gravity-engine.com/docs/server-integration/request-signing
> 说明 Gravity Engine OpenAPI 的 sign 与 Authorization 生成步骤，并提供 Java、Go 和 Python 示例代码。





调用引力服务端 openapi 接口时，部分接口需要 `Authorization` 和 `sign` 字段，本文主要给出示例，我们提供了以下 Java、Golang 和 Python 版本的示例，请参照示例生成签名。

主要分为两个方法：

1. 获取 sign
2. 获取 Authorization

现分别介绍如下。

## 1. 获取 sign [#1-获取-sign]

获取 `sign` 时，先组装 `params` 参数，参数为接口除了 `sign` 字段之外的其他所有字段的字典集合，组装完成后，调用 `getSign` 方法获取 sign 字符串。

> **注意**
> 针对 params 参数，不同的接口有不同的要求，具体请参考各个接口说明文档。

## 2. 获取 Authorization [#2-获取-authorization]

使用上一步获取的 `sign` 和 `app_key`，调用 `getAuthorization` 方法获取 `Authorization` 字符串，其中 `app_key` 为您申请的开发者应用的 key，您可以在[引力后台-设置-引力开发者](https://web.gravity-engine.com/#/manage/develop)中查看。

## 3. 代码示例 [#3-代码示例]

Python 示例依赖 `PyJWT`（安装命令：`python -m pip install PyJWT`，导入名为 `jwt`）。Java 示例依赖 Fastjson2、Guava 和 Hutool JWT；Go 示例使用所示 JWT 包，依赖版本请与项目统一。示例中的方法仅生成认证信息，不会发送请求。

签名依据服务端解析后的 JSON 值计算：对象键排序，非 ASCII 字符按 Unicode 转义，再按代码规则处理双引号和空格。不要自行增加 URL 编码、改变字段类型或遗漏实际发送的字段。跨语言使用浮点数或大整数时，还需核对数值精度和序列化结果；不要把 ID 转成浮点数。

**Java**

```java
package com.example;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.collect.Maps;
import com.google.common.hash.Hashing;
import cn.hutool.jwt.JWTUtil;
import cn.hutool.jwt.signers.JWTSigner;
import cn.hutool.jwt.signers.JWTSignerUtil;

/**
 * 引力引擎签名工具类
 */
public class SignUtil {

    /**
     * 字符串转Unicode编码
     * @param string 原始字符串
     * @return Unicode编码字符串
     */
    public static String string2Unicode(String string) {
        StringBuffer unicode = new StringBuffer();
        for (int i = 0; i < string.length(); i++) {
            char c = string.charAt(i);
            if (c < 0x20 || c > 0x7E) {
                String tmp = Integer.toHexString(c);
                if (tmp.length() >= 4) {
                    unicode.append("\\u" + Integer.toHexString(c));
                } else if (tmp.length() == 3) {
                    unicode.append("\\u0" + Integer.toHexString(c));
                } else if (tmp.length() == 2) {
                    unicode.append("\\u00" + Integer.toHexString(c));
                } else if (tmp.length() == 1) {
                    unicode.append("\\u000" + Integer.toHexString(c));
                } else {
                    unicode.append("\\u0000");
                }
            } else {
                unicode.append(c);
            }
        }
        return unicode.toString();
    }

    /**
     * 参数加签
     * @param map 请求参数
     * @param appKey 申请的引力APPKEY
     * @return 签名字符串
     */
    public static String getSign(Map<String, Object> map, String appKey) {
        List<String> params = new ArrayList<>();
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (entry.getKey().equals("sign")) {
                continue;
            }
            params.add(entry.getKey() + "=" + JSONObject.toJSONString(entry.getValue(), JSONWriter.Feature.MapSortField));
        }
        Collections.sort(params);
        StringBuilder sb = new StringBuilder();
        sb.append(Joiner.on("&").join(params));
        sb.append(appKey);
        String str = sb.toString().replaceAll("\"", "").replaceAll(" ", "");
        return Hashing.md5().newHasher().putString(string2Unicode(str), Charsets.UTF_8).hash().toString();
    }

    /**
     * 请求头加签
     * @param appKey 申请的引力APPKEY
     * @param sign 参数加签
     * @return Authorization 请求头签名
     */
    public static String getAuthorization(String appKey, String sign) {
        HashMap<String, Object> payload = Maps.newHashMap();
        payload.put("app_key", appKey);
        JWTSigner jwtSigner = JWTSignerUtil.hs256(sign.getBytes());
        return JWTUtil.createToken(payload, jwtSigner);
    }
}
```

**Golang**

```go
package test

import (
	"bytes"
	"crypto/md5"
	"encoding/json"
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"sort"
	"strings"
	"unicode/utf16"
)

// asciiEscape 与 Python json.dumps 的 ensure_ascii=True 保持一致。
func asciiEscape(s string) string {
    var buf strings.Builder
    for _, r := range s {
        if r < 0x7f {
            buf.WriteRune(r)
        } else if r <= 0xffff {
            fmt.Fprintf(&buf, "\\u%04x", r)
        } else {
            hi, lo := utf16.EncodeRune(r)
            fmt.Fprintf(&buf, "\\u%04x\\u%04x", hi, lo)
        }
    }
    return buf.String()
}

// 参数签名
// @param params map[string]interface{} 参数
// @param appKey string app key
func getSign(params map[string]interface{}, appKey string) string {
	paramsList := make([]string, 0)
	for k, v := range params {
		if k == "sign" {
			continue
		}
		var raw bytes.Buffer
		encoder := json.NewEncoder(&raw)
		encoder.SetEscapeHTML(false) // 保持 URL 中的 &、<、> 与 Python 示例一致
		if err := encoder.Encode(v); err != nil {
			panic(err) // 示例中止执行；业务代码应返回并处理错误
		}
		jsonString := asciiEscape(strings.TrimSuffix(raw.String(), "\n"))
		paramString := fmt.Sprintf("%s=%s", k, jsonString)
		paramsList = append(paramsList, paramString)
	}
	sort.Strings(paramsList)
	currentString := strings.ReplaceAll(strings.ReplaceAll(strings.Join(paramsList, "&")+appKey, "\"", ""), " ", "")
	sign := fmt.Sprintf("%x", md5.Sum([]byte(currentString)))
	return sign
}

// 获取请求 authorization
// @param sign string 签名
// @param appKey 请求key
func getAuthorization(sign, appKey string) string {
	type MyClaims struct {
		AppKey string `json:"app_key"`
		jwt.StandardClaims
	}
	claims := MyClaims{
		AppKey:         appKey,
		StandardClaims: jwt.StandardClaims{},
	}
	t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	token, _ := t.SignedString([]byte(sign))
	return token
}
```

**Python**

```python
import json
import hashlib
import jwt

def get_sign(params: dict , app_key: str) -> str:
    """
    @param params: 请求参数
    @param app_key: 引力申请的开发者app_key
    """

    param_list = []
    for k, v in params.items():
        if k == "sign":
            continue
        param_list.append(f"{k}={json.dumps(v,sort_keys=True)}")
    param_list.sort()
    sb = "&".join(param_list) + app_key
    current_str = sb.replace("\"", "").replace(" ", "")
    return hashlib.md5(current_str.encode("utf-8")).hexdigest()

def get_authorization(sign: str, app_key: str) -> str:
    """
    @param sign: 签名
    @param app_key: 引力申请的开发者app_key
    """
    return jwt.encode({"app_key": app_key}, sign, algorithm="HS256")
```

## 4. 调用顺序与排错 [#4-调用顺序与排错]

1. 组装本次实际发送的 JSON body，暂不包含 `sign`。所有可选参数、分页和筛选条件都应先确定。
2. 用开发者应用的 `app_key` 生成 `sign`，将其加入 body。
3. 用同一个 `sign` 和 `app_key` 生成 HS256 JWT，将结果原样放入请求头 `Authorization`；不要使用后台登录 Token，也不要自行添加 `Bearer ` 前缀。
4. 发送请求。修改任何字段后，包括翻页，都重新执行上述步骤。

本页算法对应 OpenAPI。事件采集 API 使用应用 Access Token；微信/抖音 Token 透传和事件回调验签各有专用规则，请参考各自接口文档。

遇到“签名验证失败”或“APPKEY验证失败”时，先确认开发者应用已审核并启用、凭证来源正确，再比较用于签名与实际发送的 body。请保留脱敏后的请求及错误信息，勿公开完整凭证。
