跳转到内容
搜索文档

保护 Stream

最后更新 查看 MarkdownAgent 设置

Signed URL / Token

默认情况下,任何拥有视频 ID 的人都可以观看 Stream 上的视频。如果您希望默认将视频设为私有且仅向特定用户提供访问权限,可以使用 signed URL 功能。当您标记视频需要 signed URL 时,仅凭视频 ID 将无法公开访问。用户将需要 signed URL token 才能观看或下载视频。

以下是使用 signed URL 的一些常见用例:

  • 限制访问,使仅登录成员可以观看特定视频
  • 让用户在有限时间段内(例如 24 小时)观看您的视频
  • 基于地理位置限制访问

使视频需要 signed URL

开启 requireSignedURLs 以使用 signed URL 保护视频。此选项将阻止 任何公开链接,例如 customer-<CODE>.cloudflarestream.com/<VIDEO_ID>/watch 或内置播放器将无法正常工作。

可以通过更新视频元数据来限制观看。

curl "https://api.cloudflare.com/client/v4/accounts/{account_id}/stream/{video_uid}" \
--header "Authorization: Bearer <API_TOKEN>" \
--header "Content-Type: application/json"
--data "{\"uid\": \"<VIDEO_UID>\", \"requireSignedURLs\": true }"

响应:

{
  "result": {
    "uid": "<VIDEO_UID>",
    ...
    "requireSignedURLs": true
  },
  "success": true,
  "errors": [],
  "messages": []
}

Workers 绑定

您也可以在 Worker 中使用 Stream 绑定要求 signed URL。请参阅绑定 Workers API了解设置说明。

export default {
	async fetch(request, env) {
		const video = await env.STREAM.video("VIDEO_ID").update({
			requireSignedURLs: true,
		});
		return Response.json(video);
	},
};
export default {
	async fetch(request, env) {
		const video = await env.STREAM.video("VIDEO_ID").update({
			requireSignedURLs: true,
		});
		return Response.json(video);
	},
};

生成 Signed Token 的三种方式

您可以通过三种方式为应用程序编程生成 token:

  • 低用量或测试:使用 /token 端点生成短期 signed token。 推荐用于测试目的,或如果您每天生成的 token 少于 1,000 个。每个 token 都需要向 Cloudflare 发起 API 调用,速率限制约束。 默认结果有效期为 1 小时。此方法不支持 Live WebRTC

  • 推荐:使用签名密钥创建 token。 如果您有数千日活用户或需要生成大量 token(如 Live WebRTC),可以使用签名密钥自行创建 token。这样,每次生成 token 时无需调用 Stream API,因此 受速率限制。

  • Workers 绑定:使用 Stream 绑定生成 token。 如果您在 Cloudflare Workers 中使用 Stream 绑定,可以直接生成 token,无需单独的 API 调用或管理签名密钥。这是 Workers 用户最简单的方法。对于访问规则或自定义过期等高级自定义,请改用签名密钥。

选项 1:使用 /token 端点

您可以为任何标记为私有的视频调用 /token 端点,获取一小时后过期的 signed URL token。此方法不支持 Live WebRTC

curl --request POST \
https://api.cloudflare.com/client/v4/accounts/{account_id}/stream/{video_uid}/token \
--header "Authorization: Bearer <API_TOKEN>"

如果请求成功,您将看到类似以下的响应:

{
	"result": {
		"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNkYzkzNTk4MmY4MDc1ZjJlZjk2MTA2ZDg1ZmNkODM4In0.eyJraWQiOiJjZGM5MzU5ODJmODA3NWYyZWY5NjEwNmQ4NWZjZDgzOCIsImV4cCI6IjE2MjE4ODk2NTciLCJuYmYiOiIxNjIxODgyNDU3In0.iHGMvwOh2-SuqUG7kp2GeLXyKvMavP-I2rYCni9odNwms7imW429bM2tKs3G9INms8gSc7fzm8hNEYWOhGHWRBaaCs3U9H4DRWaFOvn0sJWLBitGuF_YaZM5O6fqJPTAwhgFKdikyk9zVzHrIJ0PfBL0NsTgwDxLkJjEAEULQJpiQU1DNm0w5ctasdbw77YtDwdZ01g924Dm6jIsWolW0Ic0AevCLyVdg501Ki9hSF7kYST0egcll47jmoMMni7ujQCJI1XEAOas32DdjnMvU8vXrYbaHk1m1oXlm319rDYghOHed9kr293KM7ivtZNlhYceSzOpyAmqNFS7mearyQ"
	},
	"success": true,
	"errors": [],
	"messages": []
}

Workers 绑定

您可以使用 Stream 绑定生成 signed token:

export default {
	async fetch(request, env) {
		const token = await env.STREAM.video("VIDEO_ID").generateToken();
		return Response.json({ token });
	},
};
export default {
	async fetch(request, env) {
		const token = await env.STREAM.video("VIDEO_ID").generateToken();
		return Response.json({ token });
	},
};

要渲染视频或使用 manifest、缩略图等资源,请用 token 值替换 video/input ID。例如,要使用 Stream 播放器,将 cloudflarestream.com//iframe 之间的 ID 替换为 token: https://customer-<CODE>.cloudflarestream.com/<TOKEN>/iframe.

<iframe
	src="https://customer-<CODE>.cloudflarestream.com/eyJhbGciOiJSUzI1NiIsImtpZCI6ImNkYzkzNTk4MmY4MDc1ZjJlZjk2MTA2ZDg1ZmNkODM4In0.eyJraWQiOiJjZGM5MzU5ODJmODA3NWYyZWY5NjEwNmQ4NWZjZDgzOCIsImV4cCI6IjE2MjE4ODk2NTciLCJuYmYiOiIxNjIxODgyNDU3In0.iHGMvwOh2-SuqUG7kp2GeLXyKvMavP-I2rYCni9odNwms7imW429bM2tKs3G9INms8gSc7fzm8hNEYWOhGHWRBaaCs3U9H4DRWaFOvn0sJWLBitGuF_YaZM5O6fqJPTAwhgFKdikyk9zVzHrIJ0PfBL0NsTgwDxLkJjEAEULQJpiQU1DNm0w5ctasdbw77YtDwdZ01g924Dm6jIsWolW0Ic0AevCLyVdg501Ki9hSF7kYST0egcll47jmoMMni7ujQCJI1XEAOas32DdjnMvU8vXrYbaHk1m1oXlm319rDYghOHed9kr293KM7ivtZNlhYceSzOpyAmqNFS7mearyQ/iframe"
	style="border: none;"
	height="720"
	width="1280"
	allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture;"
	allowfullscreen="true"
></iframe>

同样,如果您使用自己的播放器,请通过将 manifest URL 中的 video ID 替换为 token 值来获取 HLS 或 DASH manifest:

  • https://customer-<CODE>.cloudflarestream.com/<TOKEN>/manifest/video.m3u8
  • https://customer-<CODE>.cloudflarestream.com/<TOKEN>/manifest/video.mpd

自定义默认限制

如果在不携带 body 的情况下调用 /token 端点,将返回一小时后过期、无其他限制且无法访问下载的 token。可以通过在请求中提供额外属性来自定义此 token:

	const signed_url_restrictions = {
		// Extend the lifetime of the token to 12 hours:
		exp: Math.floor(Date.now() / 1000) + 12 * 60 * 60,
		// Allow access to MP4 or Audio Download URLs:
		downloadable: true,
		// Geo or IP access restrictions:
		accessRules: {
			// ... see examples below
		}
	};

	const init = {
		method: "POST",
		headers: {
			Authorization: "Bearer <API_TOKEN>",
			"content-type": "application/json;charset=UTF-8",
		},
		body: JSON.stringify(signed_url_restrictions),
	};

	const signedurl_service_response = await fetch(
		"https://api.cloudflare.com/client/v4/accounts/{account_id}/stream/{video_uid}/token",
		init,
	);

	return new Response(
		JSON.stringify(await signedurl_service_response.json()),
		{ status: 200 },
	);

但是,如果您以编程方式生成 token 或添加此类自定义,使用签名密钥并在应用程序内完全生成 token 更快且更具扩展性。

选项 2:使用 Stream 绑定

如果在 Worker 中使用 Stream 绑定,可以在不单独调用 /token 端点或自行管理签名密钥的情况下生成 signed token。绑定在内部处理 token 生成。

请参阅绑定 Workers API了解设置说明。

export default {
	async fetch(request, env) {
		const token = await env.STREAM.video("VIDEO_ID").generateToken();
		return Response.json({ token });
	},
};
export default {
	async fetch(request, env) {
		const token = await env.STREAM.video("VIDEO_ID").generateToken();
		return Response.json({ token });
	},
};

绑定生成的 token 默认一小时后过期。如果需要自定义过期时间、地理位置或下载访问等限制,请使用签名密钥创建带有自定义声明的 token。

选项 3:使用签名密钥创建 signed token

如果您生成大量 token、使用 Live WebRTC 或需要自定义访问规则,请使用签名密钥生成新 token,这样每次无需调用 Stream API。

步骤 1:一次 调用 /stream/key 端点获取密钥

curl --request POST \
"https://api.cloudflare.com/client/v4/accounts/{account_id}/stream/keys" \
--header "Authorization: Bearer <API_TOKEN>"

响应将返回 pemjwk 值。

{
	"result": {
		"id": "8f926b2b01f383510025a78a4dcbf6a",
		"pem": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBemtHbXhCekFGMnBIMURiWmgyVGoyS3ZudlBVTkZmUWtNeXNCbzJlZzVqemRKTmRhCmtwMEphUHhoNkZxOTYveTBVd0lBNjdYeFdHb3kxcW1CRGhpdTVqekdtYW13NVgrYkR3TEdTVldGMEx3QnloMDYKN01Rb0xySHA3MDEycXBVNCtLODUyT1hMRVVlWVBrOHYzRlpTQ2VnMVdLRW5URC9oSmhVUTFsTmNKTWN3MXZUbQpHa2o0empBUTRBSFAvdHFERHFaZ3lMc1Vma2NsRDY3SVRkZktVZGtFU3lvVDVTcnFibHNFelBYcm9qaFlLWGk3CjFjak1yVDlFS0JCenhZSVEyOVRaZitnZU5ya0t4a2xMZTJzTUFML0VWZkFjdGkrc2ZqMkkyeEZKZmQ4aklmL2UKdHBCSVJZVDEza2FLdHUyYmk0R2IrV1BLK0toQjdTNnFGODlmTHdJREFRQUJBb0lCQUYzeXFuNytwNEtpM3ZmcgpTZmN4ZmRVV0xGYTEraEZyWk1mSHlaWEFJSnB1MDc0eHQ2ZzdqbXM3Tm0rTFVhSDV0N3R0bUxURTZacy91RXR0CjV3SmdQTjVUaFpTOXBmMUxPL3BBNWNmR2hFN1pMQ2wvV2ZVNXZpSFMyVDh1dGlRcUYwcXpLZkxCYk5kQW1MaWQKQWl4blJ6UUxDSzJIcmlvOW1KVHJtSUUvZENPdG80RUhYdHpZWjByOVordHRxMkZrd3pzZUdaK0tvd09JaWtvTgp2NWFOMVpmRGhEVG0wdG1Vd0tLbjBWcmZqalhRdFdjbFYxTWdRejhwM2xScWhISmJSK29PL1NMSXZqUE16dGxOCm5GV1ZEdTRmRHZsSjMyazJzSllNL2tRVUltT3V5alY3RTBBcm5vR2lBREdGZXFxK1UwajluNUFpNTJ6aTBmNloKdFdvwdju39xOFJWQkwxL2tvWFVmYk00S04ydVFadUdjaUdGNjlCRDJ1S3o1eGdvTwowVTBZNmlFNG9Cek5GUW5hWS9kayt5U1dsQWp2MkgraFBrTGpvZlRGSGlNTmUycUVNaUFaeTZ5cmRkSDY4VjdIClRNRllUQlZQaHIxT0dxZlRmc00vRktmZVhWY1FvMTI1RjBJQm5iWjNSYzRua1pNS0hzczUyWE1DZ1lFQTFQRVkKbGIybDU4blVianRZOFl6Uk1vQVo5aHJXMlhwM3JaZjE0Q0VUQ1dsVXFZdCtRN0NyN3dMQUVjbjdrbFk1RGF3QgpuTXJsZXl3S0crTUEvU0hlN3dQQkpNeDlVUGV4Q3YyRW8xT1loMTk3SGQzSk9zUythWWljemJsYmJqU0RqWXVjCkdSNzIrb1FlMzJjTXhjczJNRlBWcHVibjhjalBQbnZKd0k5aUpGVUNnWUVBMjM3UmNKSEdCTjVFM2FXLzd3ekcKbVBuUm1JSUczeW9UU0U3OFBtbHo2bXE5eTVvcSs5aFpaNE1Fdy9RbWFPMDF5U0xRdEY4QmY2TFN2RFh4QWtkdwpWMm5ra0svWWNhWDd3RHo0eWxwS0cxWTg3TzIwWWtkUXlxdjMybG1lN1JuVDhwcVBDQTRUWDloOWFVaXh6THNoCkplcGkvZFhRWFBWeFoxYXV4YldGL3VzQ2dZRUFxWnhVVWNsYVlYS2dzeUN3YXM0WVAxcEwwM3h6VDR5OTBOYXUKY05USFhnSzQvY2J2VHFsbGVaNCtNSzBxcGRmcDM5cjIrZFdlemVvNUx4YzBUV3Z5TDMxVkZhT1AyYk5CSUpqbwpVbE9ldFkwMitvWVM1NjJZWVdVQVNOandXNnFXY21NV2RlZjFIM3VuUDVqTVVxdlhRTTAxNjVnV2ZiN09YRjJyClNLYXNySFVDZ1lCYmRvL1orN1M3dEZSaDZlamJib2h3WGNDRVd4eXhXT2ZMcHdXNXdXT3dlWWZwWTh4cm5pNzQKdGRObHRoRXM4SHhTaTJudEh3TklLSEVlYmJ4eUh1UG5pQjhaWHBwNEJRNTYxczhjR1Z1ZSszbmVFUzBOTDcxZApQL1ZxUWpySFJrd3V5ckRFV2VCeEhUL0FvVEtEeSt3OTQ2SFM5V1dPTGJvbXQrd3g0NytNdWc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=",
		"jwk": "eyJ1c2UiOiJzaWciLCJrdHkiOiJSU0EiLCJraWQiOiI4ZjkyNmIyYjAxZjM4MzUxNzAwMjVhNzhhNGRjYmY2YSIsImFsZyI6IlJTMjU2IiwibiI6InprR214QnpBRjJwSDFEYlpoMlRqMkt2bnZQVU5GZlFrTXlzQm8yZWc1anpkSk5kYWtwMEphUHhoNkZxOTZfeTBVd0lBNjdYeFdHb3kxcW1CRGhpdTVqekdtYW13NVgtYkR3TEdTVldGMEx3QnloMDY3TVFvTHJIcDcwMTJxcFU0LUs4NTJPWExFVWVZUGs4djNGWlNDZWcxV0tFblREX2hKaFVRMWxOY0pNY3cxdlRtR2tqNHpqQVE0QUhQX3RxRERxWmd5THNVZmtjbEQ2N0lUZGZLVWRrRVN5b1Q1U3JxYmxzRXpQWHJvamhZS1hpNzFjak1yVDlFS0JCenhZSVEyOVRaZi1nZU5ya0t4a2xMZTJzTUFMX0VWZkFjdGktc2ZqMkkyeEZKZmQ4aklmX2V0cEJJUllUMTNrYUt0dTJiaTRHYi1XUEstS2hCN1M2cUY4OWZMdyIsImUiOiJBUUFCIiwiZCI6IlhmS3FmdjZuZ3FMZTktdEo5ekY5MVJZc1ZyWDZFV3RreDhmSmxjQWdtbTdUdmpHM3FEdU9henMyYjR0Um9mbTN1MjJZdE1UcG16LTRTMjNuQW1BODNsT0ZsTDJsX1VzNy1rRGx4OGFFVHRrc0tYOVo5VG0tSWRMWlB5NjJKQ29YU3JNcDhzRnMxMENZdUowQ0xHZEhOQXNJcllldUtqMllsT3VZZ1Q5MEk2MmpnUWRlM05oblN2MW42MjJyWVdURE94NFpuNHFqQTRpS1NnMl9sbzNWbDhPRU5PYlMyWlRBb3FmUld0LU9OZEMxWnlWWFV5QkRQeW5lVkdxRWNsdEg2Zzc5SXNpLU04ek8yVTJjVlpVTzdoOE8tVW5mYVRhd2xnei1SQlFpWTY3S05Yc1RRQ3VlZ2FJQU1ZVjZxcjVUU1Ai2odx5iT0xSX3BtMWFpdktyUSIsInAiOiI5X1o5ZUpGTWI5X3E4UlZCTDFfa29YVWZiTTRLTjJ1UVp1R2NpR0Y2OUJEMnVLejV4Z29PMFUwWTZpRTRvQnpORlFuYVlfZGsteVNXbEFqdjJILWhQa0xqb2ZURkhpTU5lMnFFTWlBWnk2eXJkZEg2OFY3SFRNRllUQlZQaHIxT0dxZlRmc01fRktmZVhWY1FvMTI1RjBJQm5iWjNSYzRua1pNS0hzczUyWE0iLCJxIjoiMVBFWWxiMmw1OG5VYmp0WThZelJNb0FaOWhyVzJYcDNyWmYxNENFVENXbFVxWXQtUTdDcjd3TEFFY243a2xZNURhd0JuTXJsZXl3S0ctTUFfU0hlN3dQQkpNeDlVUGV4Q3YyRW8xT1loMTk3SGQzSk9zUy1hWWljemJsYmJqU0RqWXVjR1I3Mi1vUWUzMmNNeGNzMk1GUFZwdWJuOGNqUFBudkp3STlpSkZVIiwiZHAiOiIyMzdSY0pIR0JONUUzYVdfN3d6R21QblJtSUlHM3lvVFNFNzhQbWx6Nm1xOXk1b3EtOWhaWjRNRXdfUW1hTzAxeVNMUXRGOEJmNkxTdkRYeEFrZHdWMm5ra0tfWWNhWDd3RHo0eWxwS0cxWTg3TzIwWWtkUXlxdjMybG1lN1JuVDhwcVBDQTRUWDloOWFVaXh6THNoSmVwaV9kWFFYUFZ4WjFhdXhiV0ZfdXMiLCJkcSI6InFaeFVVY2xhWVhLZ3N5Q3dhczRZUDFwTDAzeHpUNHk5ME5hdWNOVEhYZ0s0X2NidlRxbGxlWjQtTUswcXBkZnAzOXIyLWRXZXplbzVMeGMwVFd2eUwzMVZGYU9QMmJOQklKam9VbE9ldFkwMi1vWVM1NjJZWVdVQVNOandXNnFXY21NV2RlZjFIM3VuUDVqTVVxdlhRTTAxNjVnV2ZiN09YRjJyU0thc3JIVSIsInFpIjoiVzNhUDJmdTB1N1JVWWVubzIyNkljRjNBaEZzY3NWam55NmNGdWNGanNIbUg2V1BNYTU0dS1MWFRaYllSTFBCOFVvdHA3UjhEU0NoeEhtMjhjaDdqNTRnZkdWNmFlQVVPZXRiUEhCbGJudnQ1M2hFdERTLTlYVF8xYWtJNngwWk1Mc3F3eEZuZ2NSMF93S0V5Zzh2c1BlT2gwdlZsamkyNkpyZnNNZU9fakxvIn0=",
		"created": "2021-06-15T21:06:54.763937286Z"
	},
	"success": true,
	"errors": [],
	"messages": []
}

这些值不会再次显示,我们建议立即安全保存。 如果您使用 Cloudflare Workers,可以使用 Secrets 存储它们。如果您使用其他平台,请将其存储在安全的环境变量中。

您稍后将使用这些值生成 token。pem 和 jwk 字段为 base64 编码,使用前必须解码(步骤 2 中展示了示例)。

步骤 2:使用密钥生成 token

在步骤 1 生成密钥后,您可以使用 pemjwk 值自行生成自签名 URL。使用此方法,每次创建新 token 时无需调用 Stream API。

以下是一个 Cloudflare Worker 脚本示例,它生成 60 分钟后过期且仅适用于从英国访问视频的用户 token。在第 2 行和第 3 行,您将配置步骤 1 中的 idjwk 值:

// Global variables
const jwkKey = "{PRIVATE-KEY-IN-JWK-FORMAT}";
const keyID = "<KEY_ID>";
const videoUID = "<VIDEO_UID>";
// expiresTimeInS is the expired time in second of the video
const expiresTimeInS = 3600;

// Main function
async function streamSignedUrl() {
	const encoder = new TextEncoder();
	const expiresIn = Math.floor(Date.now() / 1000) + expiresTimeInS;
	const headers = {
		alg: "RS256",
		kid: keyID,
	};
	const data = {
		sub: videoUID,
		kid: keyID,
		exp: expiresIn,
		// Add `downloadable` boolean for access to MP4 or Audio Downloads:
		// downloadable: true,
		accessRules: [
			{
				type: "ip.geoip.country",
				action: "allow",
				country: ["GB"],
			},
			{
				type: "any",
				action: "block",
			},
		],
	};

	const token = `${objectToBase64url(headers)}.${objectToBase64url(data)}`;

	const jwk = JSON.parse(atob(jwkKey));

	const key = await crypto.subtle.importKey(
		"jwk",
		jwk,
		{
			name: "RSASSA-PKCS1-v1_5",
			hash: "SHA-256",
		},
		false,
		["sign"],
	);

	const signature = await crypto.subtle.sign(
		{ name: "RSASSA-PKCS1-v1_5" },
		key,
		encoder.encode(token),
	);

	const signedToken = `${token}.${arrayBufferToBase64Url(signature)}`;

	return signedToken;
}

// Utilities functions
function arrayBufferToBase64Url(buffer) {
	return btoa(String.fromCharCode(...new Uint8Array(buffer)))
		.replace(/=/g, "")
		.replace(/\+/g, "-")
		.replace(/\//g, "_");
}

function objectToBase64url(payload) {
	return arrayBufferToBase64Url(
		new TextEncoder().encode(JSON.stringify(payload)),
	);
}

步骤 3:渲染视频

如果您使用 Stream Player,请将步骤 2 中 Worker 返回的 token 值替换视频 ID,替换 cloudflarestream.com//iframe 之间的整个字符串:

<iframe
	src="https://customer-<CODE>.cloudflarestream.com/eyJhbGciOiJSUzI1NiIsImtpZCI6ImNkYzkzNTk4MmY4MDc1ZjJlZjk2MTA2ZDg1ZmNkODM4In0.eyJraWQiOiJjZGM5MzU5ODJmODA3NWYyZWY5NjEwNmQ4NWZjZDgzOCIsImV4cCI6IjE2MjE4ODk2NTciLCJuYmYiOiIxNjIxODgyNDU3In0.iHGMvwOh2-SuqUG7kp2GeLXyKvMavP-I2rYCni9odNwms7imW429bM2tKs3G9INms8gSc7fzm8hNEYWOhGHWRBaaCs3U9H4DRWaFOvn0sJWLBitGuF_YaZM5O6fqJPTAwhgFKdikyk9zVzHrIJ0PfBL0NsTgwDxLkJjEAEULQJpiQU1DNm0w5ctasdbw77YtDwdZ01g924Dm6jIsWolW0Ic0AevCLyVdg501Ki9hSF7kYST0egcll47jmoMMni7ujQCJI1XEAOas32DdjnMvU8vXrYbaHk1m1oXlm319rDYghOHed9kr293KM7ivtZNlhYceSzOpyAmqNFS7mearyQ/iframe"
	style="border: none;"
	height="720"
	width="1280"
	allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture;"
	allowfullscreen="true"
></iframe>

如果您使用自己的播放器,请将 manifest URL 中的视频 ID 替换为 token 值:

https://customer-<CODE>.cloudflarestream.com/eyJhbGciOiJSUzI1NiIsImtpZCI6ImNkYzkzNTk4MmY4MDc1ZjJlZjk2MTA2ZDg1ZmNkODM4In0.eyJraWQiOiJjZGM5MzU5ODJmODA3NWYyZWY5NjEwNmQ4NWZjZDgzOCIsImV4cCI6IjE2MjE4ODk2NTciLCJuYmYiOiIxNjIxODgyNDU3In0.iHGMvwOh2-SuqUG7kp2GeLXyKvMavP-I2rYCni9odNwms7imW429bM2tKs3G9INms8gSc7fzm8hNEYWOhGHWRBaaCs3U9H4DRWaFOvn0sJWLBitGuF_YaZM5O6fqJPTAwhgFKdikyk9zVzHrIJ0PfBL0NsTgwDxLkJjEAEULQJpiQU1DNm0w5ctasdbw77YtDwdZ01g924Dm6jIsWolW0Ic0AevCLyVdg501Ki9hSF7kYST0egcll47jmoMMni7ujQCJI1XEAOas32DdjnMvU8vXrYbaHk1m1oXlm319rDYghOHed9kr293KM7ivtZNlhYceSzOpyAmqNFS7mearyQ/manifest/video.m3u8

要允许访问 MP4 或音频下载,请确保视频已启用下载类型。然后在生成 signed URL 时按上方注释所示向负载添加 downloadable: true。将下载 URL 中的视频 ID 替换为 token 值:

  • https://customer-<CODE>.cloudflarestream.com/eyJhbGciOiJ.../downloads/default.mp4

撤销密钥

您最多可以创建 1,000 个密钥并随时轮换。 撤销后,使用该密钥创建的所有 token 都将失效。

curl --request DELETE \
"https://api.cloudflare.com/client/v4/accounts/{account_id}/stream/keys/{key_id}" \
--header "Authorization: Bearer <API_TOKEN>"

# 响应:
{
  "result": "Revoked",
  "success": true,
  "errors": [],
  "messages": []
}

支持的限制

属性名称 描述
exp 过期时间。token 停止生效的 Unix epoch 时间戳。不能晚于签名时间起 24 小时之后
nbf Not Before(不早于)值。token 生效前的 Unix epoch 时间戳
downloadable 如果为 true,token 可用于下载 mp4(假设视频已启用下载)
accessRules 指定一个或多个 IP 和地理限制的数组。accessRules 按顺序评估。如果规则匹配,将应用关联操作且不再评估后续规则。token 的 accessRules 数组最多可有 5 个成员。

accessRules 架构

每个 accessRule 必须包含 2 个必需属性:

  • type:支持的值为 anyip.srcip.geoip.country
  • action:支持的值为 allowblock

根据规则类型,accessRules 支持 2 个额外属性:

  • countryISO 3166-1 Alpha 2 格式的 2 字母国家/地区代码数组。
  • ip:IP 范围数组。如有可能,建议在规则中同时包含 IPv4 和 IPv6 变体。规则中只有单一变体意味着该规则将忽略另一变体。例如,基于 IPv4 的规则永远不会适用于从 IPv6 地址连接的观看者。应优先使用 CIDR 而非特定 IP 地址。某些设备(如手机)在观看过程中可能会更改 IP。视频访问控制在视频播放期间持续评估。因此,过于严格的 IP 规则可能会中断播放。

示例 1:阻止来自特定国家/地区的观看

...
"accessRules": [
	{
		"type": "ip.geoip.country",
		"action": "block",
		"country": ["US", "DE", "MX"],
	},
]

第一条规则在此匹配国家/地区 US、DE 和 MX。当该规则匹配时,block 操作将使 token 被视为无效。如果第一条规则不匹配,则没有更多规则需要评估。此情况下的行为是将 token 视为有效。

示例 2:仅允许来自特定国家/地区或 IP 的观看

...
"accessRules": [
	{
		"type": "ip.geoip.country",
		"country": ["US", "MX"],
		"action": "allow",
	},
	{
		"type": "ip.src",
		"ip": ["93.184.216.0/24", "2400:cb00::/32"],
		"action": "allow",
	},
	{
		"type": "any",
		"action": "block",
	},
]

第一条规则在此匹配国家/地区 US 和 MX。当该规则匹配时,allow 操作将使 token 被视为有效。如果不匹配,我们继续评估规则

第二条规则是匹配 CIDR 93.184.216.0/24 和 2400:cb00::/32 的 IP 规则。当该规则匹配时,allow 操作将使规则被视为有效。

如果前两条规则都不匹配,最后的 any 规则将匹配所有剩余请求并阻止这些观看。

安全注意事项

热链接保护

默认情况下,Stream 嵌入代码可在任何域名上使用。如有需要,您可以从 Stream 仪表板限制视频可嵌入的域名。

在仪表板中,每个视频旁会显示标记为 Enter allowed origin domains separated by commas(输入允许的源域名,以逗号分隔)的文本框。点击后可以列出 Stream 嵌入代码允许使用的域名。

  • *.badtortilla.com 覆盖 a.badtortilla.coma.b.badtortilla.com,但不覆盖 badtortilla.com
  • example.com 不覆盖 www.example.com 或 example.com 的任何子域名
  • 如果 localhost 未在 80 端口(HTTP)或 443 端口(HTTPS)上提供服务,则需要指定端口
  • 不支持路径 — example.com 覆盖 example.com/*

您也可以使用 Stream API 以编程方式控制嵌入限制。以下示例中的 uid 指视频 ID。

curl https://api.cloudflare.com/client/v4/accounts/{account_id}/stream/{video_uid} \
--header "Authorization: Bearer <API_TOKEN>" \
--data "{\"uid\": \"<VIDEO_UID>\", \"allowedOrigins\": [\"example.com\"]}"

您也可以使用 Stream 绑定设置允许的来源:

export default {
	async fetch(request, env) {
		const video = await env.STREAM.video("VIDEO_ID").update({
			allowedOrigins: ["example.com"],
		});
		return Response.json(video);
	},
};
export default {
	async fetch(request, env) {
		const video = await env.STREAM.video("VIDEO_ID").update({
			allowedOrigins: ["example.com"],
		});
		return Response.json(video);
	},
};

允许的来源(Allowed Origins)

Allowed Origins 功能让您指定允许播放的来源。即使您使用自己的视频播放器,此功能也有效。使用自己的播放器时,该功能会限制可以从哪些域名请求 HLS/DASH manifest 和视频片段。

Signed URL

将 signed URL 与嵌入限制结合使用,可以严格控制视频的观看方式。这样您可以仅向受信任的用户提供服务,同时防止 signed URL 被托管在未知站点上。

这篇文档对您有帮助吗?