本文为您介绍 HTTP 回调鉴权的原理及使用说明。
视频点播支持在 HTTP/HTTPS 回调时增加特定签名头,以便回调消息接收服务端进行签名认证,用来防止非法或无效请求。HTTP 回调鉴权是否开启由您自行决定,但视频点播建议您开启。如果您在开启了回调鉴权并设置了鉴权密钥,则回调时会携带所有鉴权相关内容,供回调消息接收服务端进行鉴权使用。
视频点播在回调消息头部增加的具体鉴权参数如下所示。
字段 | 说明 |
|---|---|
X-VOD-TIMESTAMP | 回调请求发起时间。Unix 时间戳,精度为秒。 |
X-VOD-SIGNATURE | 签名字符串,为 32 位 MD5 值。详细说明请见签名算法。 |
X-VOD-SIGNATURE 的计算依赖以下字段:
字段 | 说明 | 示例 |
|---|---|---|
回调 URL | 您在视频点播控制台设置的回调消息接收地址。 | https://www.example1.com/your/callback |
X-VOD-TIMESTAMP | 回调请求发起时间。Unix 时间戳,精度为秒。 | 1545675780 |
PrivateKey | 您在视频点播控制台设置的鉴权密钥。 | ABCDabcd1234 |
CallbackDataJsonBase64Encode | Base64Encode 处理后的回调数据。 注意 请不要去掉空格。 | ewoiYSI6MSwKImIiOjIKfQ== |
将上述四个字段按照上述顺序进行拼接,字段中间以竖线(|)分割,然后计算 MD5 值。示例如下所示:
CallbackContent = base64Encode(CallbackDataJson) MD5Content = 回调URL|X-VOD-TIMESTAMP|PrivateKey|CallbackContent X-VOD-SIGNATURE = md5sum(MD5Content)
说明
由于时间设置等问题,时间差值可能会存在误差,服务端可自行决定是否进行校验。
视频点播为您提供 Go、Python、Java、PHP 语言的回调鉴权 Demo。
Go Demo 如下:
func Callback(r *http.Request) { timestamp := r.Header.Get("X-VOD-TIMESTAMP") // 可以校验 X-VOD-TIMESTAMP 与当前时间,不能超过某个范围 tm, _ := strconv.ParseInt(timestamp, 10, 64) if time.Now().Unix() - tm > consts.MaxRequestTime { // 超过某个范围验签不通过 } // 请求 body 进行 base64 编码 body, _ := io.ReadAll(r.Body) callbackContent := base64.StdEncoding.EncodeToString(body) // 拼接签算内容,竖线分割,规则为回调 URL|X-VOD-TIMESTAMP|PrivateKey|CallbackContent // 因为修改 key 后配置生效有延迟,因此修改 key 时建议兼容两种 key 的配置,这里只给出一个 key 的计算结果 md5Content := fmt.Sprintf("%s|%s|%s|%s", url, timestamp, privateKey, callbackContent) //计算拼接完内容的 md5 hash := md5.New() hash.Write([]byte(md5Content)) sign := hex.EncodeToString(hash.Sum(nil)) //判断与 Header:X-VOD-SIGNATURE 是否一致 if sign == r.Header.Get("X-VOD-SIGNATURE") { //todo 验签通过 } else { //todo 验签不通过 } }
Python Demo 如下:
import hashlib import base64 def callback(): url = request.url content = str(base64.b64encode(request.data), 'utf-8') # header 大小写不敏感,需要用get获取 timestamp = request.headers.get('X-VOD-TIMESTAMP') # 可以校验 X-VOD-TIMESTAMP 与当前时间,不能超过某个范围 # todo # 验签规则,竖线分割,规则为回调 URL|X-VOD-TIMESTAMP|PrivateKey|CallbackContent # 因为修改 key 后配置生效有延迟,因此修改 key 时建议兼容两种 key 的配置,这里只给出一个 key 的计算结果 private_key = 'privateKey' signature = hashlib.md5("{}|{}|{}|{}".format(url, timestamp, private_key, content).encode("utf-8")).hexdigest() if signature == request.headers.get('X-VOD-SIGNATURE'): # 鉴权通过 else: # 鉴权失败 json_data = {"code": 0, "message": "success"} return jsonify(json_data)
Java Demo 如下:
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; public class MyCallbackHandler { public String callback(HttpServletRequest request, HttpServletResponse response) { String url = request.getRequestURL().toString(); String content = new String(Base64.getEncoder().encode(getRequestBody(request)), StandardCharsets.UTF_8); // header 大小写不敏感,需要用 getHeader 获取 String timestamp = request.getHeader("X-VOD-TIMESTAMP"); // 可以校验 X-VOD-TIMESTAMP 与当前时间,不能超过某个范围 // todo // 验签规则,竖线分割,规则为回调 URL|X-VOD-TIMESTAMP|PrivateKey|CallbackContent // 因为修改 key 后配置生效有延迟,因此修改 key 时建议兼容两种 key 的配置,这里只给出一个 key 的计算结果 String privateKey = "privateKey"; String signature = calculateSignature(url, timestamp, privateKey, content); if (signature.equals(request.getHeader("X-VOD-SIGNATURE"))) { // 鉴权通过 } else { //鉴权失败 } String jsonResponse = "{"code": 0, "message": "success"}"; return jsonResponse; } private byte[] getRequestBody(HttpServletRequest request) { // 获取请求体内容并转为字节数组 try (InputStream inputStream = request.getInputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } return outputStream.toByteArray(); } catch (IOException e) { e.printStackTrace(); return new byte[0]; } } private String calculateSignature(String url, String timestamp, String privateKey, String content) { String message = url + "|" + timestamp + "|" + privateKey + "|" + content; try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] digest = md.digest(message.getBytes(StandardCharsets.UTF_8)); StringBuilder sb = new StringBuilder(); for (byte b : digest) { sb.append(String.format("%02x", b)); } return sb.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); return null; } } }
PHP Demo 如下:
<?php function callback() { $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 'https://' : 'http://'; $url = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; $content = base64_encode(file_get_contents('php://input')); // header 大小写不敏感,需要用 get 获取 $timestamp = $_SERVER['HTTP_X_VOD_TIMESTAMP']; // 可以校验 X-VOD-TIMESTAMP 与当前时间,不能超过某个范围 // todo // 验签规则,竖线分割,规则为回调 URL|X-VOD-TIMESTAMP|PrivateKey|CallbackContent // 因为修改 key 后配置生效有延迟,因此修改 key 时建议兼容两种 key 的配置,这里只给出一个 key 的计算结果 $private_key = 'privateKey'; $signature = md5($url . '|' . $timestamp . '|' . $private_key . '|' . $content); if ($signature == $_SERVER['HTTP_X_VOD_SIGNATURE']) { // 鉴权通过 } else { // 鉴权失败 } $json_data = array("code" => 0, "message" => "success"); return json_encode($json_data); } ?>
您在切换鉴权密钥时,为保证回调鉴权不受影响,您的回调消息接收服务端需要兼容新旧两个 鉴权密钥的平滑切换,即在一段时间内兼容新旧两个鉴权密钥的鉴权。建议参考以下更换流程: