logologo
售前咨询
直播云
产品简介
产品定价
控制台操作指南
客户端SDK
服务端API
域名管理相关接口
证书管理相关接口
直播流管理相关接口
转码管理相关接口
录制管理相关接口
用量查询相关接口
直播质量相关接口
直播回调相关接口
地址管理相关接口
相关协议
文档中心
服务端API签名方法

签名方法


签名算法

为了保证网关层请求者身份的合法性和请求参数在传输过程中的安全性,OpenAPI 需要对开放服务的请求引入签名机制,对请求参数进行规范性的预处理和 Hash 运算获取请求签名。请求签名将和请求参数一起发送到OpenAPI,同时OpenAPI采用同样的机制对收到的请求进行签名计算,并与请求中的签名进行匹配。网关层对于所有签名不匹配的请求一律不允放行。

签名参数准备

  • 通过账号系统控制台,获取请求用户的密钥对( AccessKey / SecretKey )
  • 按照 OpenAPI 开放服务文档,构建服务请求
  • 请求需要统一添加必填的公共参数

构建正规化请求字符串

准备好签名参数和按照选定需要的服务构建服务请求后,需要对服务请求参数进行正规化处理。正规化请求(CanonicalRequest)的组成规则如下:

CanonicalRequest =
  HTTPRequestMethod + '\n' +
  CanonicalURI + '\n' +
  CanonicalQueryString + '\n' +
  CanonicalHeaders + '\n' +
  SignedHeaders + '\n' +
  HexEncode(Hash(RequestPayload))

组成字段说说明:

字段 说明
HTTPRequestMethod 指代http请求的method,例如:GET、POST等。
OpenAPI 推荐所有请求以POST方式发起
CanonicalURI 指代正规化后的URI。
如果URI为空,那么使用"/"作为绝对路径。
OpenAPI中,主要接口的的URI设定都为"/"。如果是复杂的path,请通过RFC3986规范进行编码。
CanonicalQueryString 指代正规化后的Query String。
对于Query String的正规化大致的过程如下:1. urlencode(注:同RFC3986方法)每一个querystring参数名称和参数值。2. 按照ASCII字节顺序对参数名称严格排序,相同参数名的不同参数值需保持请求的原始顺序。3. 将排序好的参数名称和参数值用=连接,按照排序结果将“参数对”用&连接。例如:CanonicalQueryString = "Action=FetchUpload&Version=2021-11-09"
CanonicalHeaders 指代正规化后的Header字符串。
组成规则如下:CanonicalHeaders = CanonicalHeadersEntry0 + CanonicalHeadersEntry1 + ... + CanonicalHeadersEntryN 其中 CanonicalHeadersEntry = Lowercase(HeaderName) + ':' + Trimall(HeaderValue) + '\n' Lowcase代表将Header的名称全部转化成小写,Trimall表示去掉Header的值的前后多余的空格。特别注意:最后需要添加"\n"的换行符,header的顺序是以headerName的小写后ascii排序 与SignedHeaders中的header顺序需保持一致。
SignedHeaders 指代参与签名的header名称。
签名header包含在正规化headers名称列表中,其目的是指明哪些header参与签名计算。其中X-SL-Action必须添加。构造方式如下:SignedHeaders = Lowercase(HeaderName0) + ';' + Lowercase(HeaderName1) + ";" + ... + Lowercase(HeaderNameN)
RequestPayload 指代完整的请求的body。

构建签名字符串

签名字符串主要包含请求以及正规化请求的元数据信息,由签名算法、请求日期、SL规则串和正规化请求哈希值连接组成。

StringToSign = 
  Algorithm + '\n' + 
  Timestamp + '\n' +
  CredentialScope + '\n' + 
  HexEncode(Hash(CanonicalRequest))

组成字段说明:

字段 解释
Algorithm 签名的算法
目前仅支持HMAC-SHA256的签名算法,目前固定为 SL-HMAC-SHA256。
TimeStamp 请求UTC时间
即请求头公共参数中X-SL-Timestamp的取值
CredentialScope 凭证范围
格式为 Date/service/sl_request,包含日期、所请求的服务和终止字符串(sl_request)。
Date UTC 标准时间的日期
取值需要和公共参数 X-TC-Timestamp 换算的 UTC 标准时间日期一致。
service 产品名,必须与调用的产品域名一致,直播云服务的产品域名为'live'。
例: 2022-02-25/live/sl_request。
CanonicalRequest 指代按照 1.3.2 生成的正规化请求字符串。

计算签名密钥

签名密钥需要避免直接使用用户的基础密钥,而是应当以用户的基础密钥为根密钥,通过计算派生出符合请求的签名密钥,防止用户密钥的扩散带来的不安全性。签名密钥的计算规则如下:

SLSecret = [用户AccessKey对应的SecretKey]
SLDate = HMAC(SLSecret, Date)
SLService = HMAC(SLDate, Service)
SLSigning = HMAC(SLService, "sl_request")

计算最终请求签名

使用签名字符串和签名密钥计算获取最终的请求签名

Signature = HexEncode(HMAC(SLSigning, StringToSign))

拼接Authorization

Authorization =
    Algorithm + ' ' +
    'Credential=' + AccessKey + '/' + CredentialScope + ', ' +
    'SignedHeaders=' + SignedHeaders + ', ' +
    'Signature=' + Signature + "sl_request"

签名计算示例

对于一个需要进行FetchUpload的API请求。假定使用方已经在账号系统中创建了一个指定用户,并获取到此用户对应的AK/SK。

AccessKey:"3af394d65d654582bd6e8ad122199558"
SecretKey:"88d749f980554ca79bc6ff9b2ce02c10"

步骤1: 创建正规请求化请求字符串

CanonicalRequest =
  "POST" + '\n' +
  "/" + '\n' +
  "Action=DescribeLicense" + '\n' +
  "content-type:application/x-www-form-urlencoded\nhost:streamlake-api.staging.kuaishou.com" + '\n' +
  "content-type;host" + '\n' +
  "c2ef249dbee06fcf906069b4900cc806ddcfdecbaa87552439b87d0ce6ad7e45" //HexEncode(Hash("PackageId=com.kwai.facialassistant.demo&ProdCode=y-tech&Version=2022-02-25"))

步骤2: 创建签名字符串

StringToSign = 
  "SL_HMAC-SHA256" + '\n' + 
  "1658215855" + '\n' +
  "2022-07-19/license/sl_request" + '\n' + 
  "32544b380cd36218b30f6bb6d0bd52b163c997775108893beb1668132a3e9676" //HexEncode(Hash(CanonicalRequest))

步骤3: 计算最终签名

Signature="d57996a78008bf1e505f1d677afbfb89d9097f61226b2ca64876bb7523db9f3e"//HexEncode(HMAC(SLSigning, StringToSign))

步骤4:拼接Authorization

Authorization= "SL-HMAC-SHA256 Credential=3af394d65d654582bd6e8ad122199558/2022-07-19/license/sl_request, SignedHeaders=content-type;host, Signature=d57996a78008bf1e505f1d677afbfb89d9097f61226b2ca64876bb7523db9f3esl_request"

步骤5: 签名添加到Header中

Authorization:SL-HMAC-SHA256 Credential=3af394d65d654582bd6e8ad122199558/2022-07-19/license/sl_request, SignedHeaders=content-type;host, Signature=d57996a78008bf1e505f1d677afbfb89d9097f61226b2ca64876bb7523db9f3esl_request

加签示例

加签参数实体

/**
 * SignatureVO
 */
public class SignatureVO {
    /**
     * StreamLake 密钥ak
     */
    private String accessKeyId;
    /**
     * StreamLake 密钥sk
     */
    private String accessKeySecret;
    /**
     * StreamLake 加签算法
     */
    private String algorithm;
    /**
     * StreamLake 服务编码
     */
    private String service;
    /**
     * StreamLake request host
     */
    private String host;
    /**
     * StreamLake request content-type
     */
    private String contentType;
    /**
     * StreamLake request region
     */
    private String region;
    /**
     * StreamLake request action
     */
    private String action;
    /**
     * StreamLake request version
     */
    private String version;
    /**
     * HTTP 请求方法(GET、POST )
     */
    private String httpRequestMethod;
    /**
     * 发起 HTTP 请求 URL 中的查询字符串,
     * 对于 GET 请求,则为 URL 中问号(?)后面的字符串内容,例如:Limit=10&Offset=0。
     */
    private String canonicalQueryString;
    /**
     * 参与签名的头部信息,
     * 至少包含 host 和 content-type 两个头部,
     * 也可加入自定义的头部参与签名以提高自身请求的唯一性和安全性。
     */
    private String canonicalHeaders;
    /**
     * 参与签名的头部信息,说明此次请求有哪些头部参与了签名,
     * 和 CanonicalHeaders 包含的头部内容是一一对应的。
     * content-type 和 host 为必选头部。
     */
    private String signedHeaders;
    /**
     * 请求正文
     */
    private String payload;

    public String getAccessKeyId() {
        return accessKeyId;
    }

    public void setAccessKeyId(String accessKeyId) {
        this.accessKeyId = accessKeyId;
    }

    public String getAccessKeySecret() {
        return accessKeySecret;
    }

    public void setAccessKeySecret(String accessKeySecret) {
        this.accessKeySecret = accessKeySecret;
    }

    public String getAlgorithm() {
        return algorithm;
    }

    public void setAlgorithm(String algorithm) {
        this.algorithm = algorithm;
    }

    public String getService() {
        return service;
    }

    public void setService(String service) {
        this.service = service;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public String getContentType() {
        return contentType;
    }

    public void setContentType(String contentType) {
        this.contentType = contentType;
    }

    public String getRegion() {
        return region;
    }

    public void setRegion(String region) {
        this.region = region;
    }

    public String getAction() {
        return action;
    }

    public void setAction(String action) {
        this.action = action;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getHttpRequestMethod() {
        return httpRequestMethod;
    }

    public void setHttpRequestMethod(String httpRequestMethod) {
        this.httpRequestMethod = httpRequestMethod;
    }

    public String getCanonicalQueryString() {
        return canonicalQueryString;
    }

    public void setCanonicalQueryString(String canonicalQueryString) {
        this.canonicalQueryString = canonicalQueryString;
    }

    public String getCanonicalHeaders() {
        return canonicalHeaders;
    }

    public void setCanonicalHeaders(String canonicalHeaders) {
        this.canonicalHeaders = canonicalHeaders;
    }

    public String getSignedHeaders() {
        return signedHeaders;
    }

    public void setSignedHeaders(String signedHeaders) {
        this.signedHeaders = signedHeaders;
    }

    public String getPayload() {
        return payload;
    }

    public void setPayload(String payload) {
        this.payload = payload;
    }

    public SignatureVO() {
    }
}

加签示例 (Java)

/**
 * 
 */
@Slf4j
@Component
public class StandardAccessUtils {

    private static final Charset UTF8 = StandardCharsets.UTF_8;

    public static void signatureAdd(SignatureVO signatureVO) throws Exception {
        String accessKeyId = signatureVO.getAccessKeyId();
        String accessKeySecret = signatureVO.getAccessKeySecret();
        String service = signatureVO.getService();
        String host = signatureVO.getHost();
        String contentType = signatureVO.getContentType();
        String region = signatureVO.getRegion();
        String action = signatureVO.getAction();
        String version = signatureVO.getVersion();
        String algorithm = signatureVO.getAlgorithm();
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        // 注意时区,否则容易出错
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
        // Date 为 UTC 标准时间的日期,取值需要和公共参数 X-TC-Timestamp 换算的 UTC 标准时间日期一致
        String date = sdf.format(new Date(Long.parseLong(timestamp + "000")));

        // ************* 步骤 1:拼接规范请求串 *************
        String httpRequestMethod = signatureVO.getHttpRequestMethod();
        String canonicalUri = "/";
        String canonicalQueryString = signatureVO.getCanonicalQueryString();
        String canonicalHeaders = signatureVO.getCanonicalHeaders();
        String signedHeaders = signatureVO.getSignedHeaders();

        String payload = signatureVO.getPayload();
        String hashedRequestPayload = sha256Hex(payload);
        String canonicalRequest =
                httpRequestMethod + "\n"
                        + canonicalUri + "\n"
                        + canonicalQueryString + "\n"
                        + canonicalHeaders + "\n"
                        + signedHeaders + "\n"
                        + hashedRequestPayload;

        // ************* 步骤 2:拼接待签名字符串 *************
        String credentialScope = date + "/" + service + "/" + "sl_request";
        String hashedCanonicalRequest = sha256Hex(canonicalRequest);
        String stringToSign =
                algorithm + "\n"
                        + timestamp + "\n"
                        + credentialScope + "\n"
                        + hashedCanonicalRequest;

        // ************* 步骤 3:计算签名 *************
        byte[] secretDate = hmac256(("SL" + accessKeySecret).getBytes(UTF8), date);
        byte[] secretService = hmac256(secretDate, service);
        byte[] secretSigning = hmac256(secretService, "sl_request");
        String signature = DatatypeConverter.printHexBinary(hmac256(secretSigning, stringToSign)).toLowerCase();

        // ************* 步骤 4:拼接 Authorization *************
        String authorization = algorithm + " " + "Credential=" + accessKeyId + "/" + credentialScope + ", "
                + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature + "sl_request";

        TreeMap<String, String> header = new TreeMap<String, String>();
        header.put("Authorization", authorization);
        header.put("Content-Type", contentType);
        header.put("Host", host);
        header.put("X-SL-Action", action);
        header.put("X-SL-Timestamp", timestamp);
        header.put("X-SL-Version", version);
        header.put("X-SL-Region", region);
        header.put("X-SL-Program-Language", "Java");
        header.put("SignatureVersion", "1");
        header.put("AccessKey", accessKeyId);

        StringBuilder sb = new StringBuilder();
        sb.append("curl -X POST https://").append(host).append("/?Action=").append(action)
                .append(" -H "Authorization: ").append(authorization).append(""")
                .append(" -H "Content-Type: ").append(contentType).append(""")
                .append(" -H "Host: ").append(host).append(""")
                .append(" -H "X-SL-Action: ").append(action).append(""")
                .append(" -H "X-SL-Timestamp: ").append(timestamp).append(""")
                .append(" -H "X-SL-Version: ").append(version).append(""")
                .append(" -H "X-SL-Region: ").append(region).append(""")
                .append(" -H "X-SL-Program-Language: ").append(programLanguage).append(""")
                .append(" -H "SignatureVersion: ").append("1").append(""")
                .append(" -H "AccessKey: ").append(accessKeyId).append(""")
                .append(" -d '").append(payload).append("'");
        System.out.println(sb.toString());
        return header;
    }

    public static byte[] hmac256(byte[] key, String msg) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
        mac.init(secretKeySpec);
        return mac.doFinal(msg.getBytes(UTF8));
    }

    public static String sha256Hex(String s) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] d = md.digest(s.getBytes(UTF8));
        return DatatypeConverter.printHexBinary(d).toLowerCase();
    }

    public static void main(String[] args) throws Exception {
        SignatureVO signatureVO = SignatureVO.builder()
                .accessKeyId("xxx")
                .accessKeySecret("xxx")
                .algorithm("SL-HMAC-SHA256")
                .action("FetchUpload")
                .host("vod.streamlakeapi.com")
                .contentType("application/json")
                .region("beijing")
                .version("2022-06-23")
                .service("vod")
                .canonicalHeaders("content-type:application/json\nhost:vod.streamlakeapi.com")
                .canonicalQueryString("Action=FetchUpload")
                .httpRequestMethod("POST")
                .signedHeaders("content-type;host")
                .payload("{\"URLSets\":[{\"MediaURL\":\"http://j.com/mediacloud/demo/test.mp4\",\"CallbackArgs\":\"test\"}]}")
                .build();
        signatureAdd(signatureVO);
    }
}
上一篇:公共参数下一篇:返回结果
该篇文档内容是否对您有帮助?
有帮助没帮助