本文为您介绍 HTTP 回调事件通知的回调机制、鉴权原理及使用流程。
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)
回调消息接收服务端校验流程
- 回调消息接收服务端将接收到的回调 JSON 进行 Base64 编码。
- 回调消息接收服务端将回调所设置的回调 URL、X-VOD-TIMESTAMP 取值、PrivateKey 取值、Base64Encode 处理后的回调 JSON 拼接后,计算 MD5 值,得到加密字符串,再将加密字符串与回调请求所带的 X-VOD-SIGNATURE 字段进行对比。如果不一致,那么请求是非法的。
- (可选)回调消息接收服务端获取当前时间,与回调请求所带的 X-VOD-TIMESTAMP 字段时间相减。如果超过服务端所设定的指定时间(如 8 分钟,由服务端自行定义),则认为该请求无效。
说明
由于时间设置等问题,时间差值可能会存在误差,服务端可自行决定是否进行校验。
回调消息接收服务端 Demo
视频点播为您提供 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);
}
?>
更换鉴权密钥
您在切换鉴权密钥时,为保证回调鉴权不受影响,您的回调消息接收服务端需要兼容新旧两个 鉴权密钥的平滑切换,即在一段时间内兼容新旧两个鉴权密钥的鉴权。
建议参考以下更换流程:
- 您自行定义新的鉴权密钥。
- 升级回调消息接收服务端,兼容新旧两个鉴权密钥的鉴权。
- 在点播控制台将鉴权密钥更新成最新。
- 等待一段时间后,回调消息接收服务端去掉对原来鉴权密钥的兼容。