INTEGRITY Документация

Обслуживание приватных изображений

Приватные изображения можно отдавать с помощью токенов подписанных URL. Если для изображения требуется подписанный URL, доступ к нему без токена невозможен, если только оно не запрашивается для варианта, для которого разрешён публичный доступ всегда.

  1. На панели управления Cloudflare перейдите к разделу Размещённые изображения страницу.

    Перейдите в Размещённые изображения ↗
  2. Выберите Ключи.

  3. Скопируйте свой ключ и используйте его для создания URL с ограниченным сроком действия токена.

Создание подписанных URL на бэкенде

Подписанные URL-адреса создаются на стороне сервера, чтобы защитить ваш ключ подписи. В примере ниже используется Cloudflare Worker, но такую же логику подписи можно реализовать в любой серверной среде (Node.js, Python, PHP, Go и т. д.).

Worker принимает обычный URL-адрес Images и возвращает подписанный URL-адрес, срок действия которого истекает через один день. Измените EXPIRATION значение, чтобы задать другой срок действия.

const EXPIRATION = 60 * 60 * 24; // 1 day

const bufferToHex = (buffer) =>
	[...new Uint8Array(buffer)]
		.map((x) => x.toString(16).padStart(2, "0"))
		.join("");

async function generateSignedUrl(url, signingKey) {
	// `url` is a full imagedelivery.net URL
	// e.g. https://imagedelivery.net/cheeW4oKsx5ljh8e8BoL2A/bc27a117-9509-446b-8c69-c81bfeac0a01/mobile

	const encoder = new TextEncoder();
	const secretKeyData = encoder.encode(signingKey);
	const key = await crypto.subtle.importKey(
		"raw",
		secretKeyData,
		{ name: "HMAC", hash: "SHA-256" },
		false,
		["sign"],
	);

	// Attach the expiration value to the URL
	const expiry = Math.floor(Date.now() / 1000) + EXPIRATION;
	url.searchParams.set("exp", expiry);
	// `url` now looks like
	// https://imagedelivery.net/cheeW4oKsx5ljh8e8BoL2A/bc27a117-9509-446b-8c69-c81bfeac0a01/mobile?exp=1631289275

	const stringToSign = url.pathname + "?" + url.searchParams.toString();
	// e.g. /cheeW4oKsx5ljh8e8BoL2A/bc27a117-9509-446b-8c69-c81bfeac0a01/mobile?exp=1631289275

	// Generate the HMAC signature
	const mac = await crypto.subtle.sign(
		"HMAC",
		key,
		encoder.encode(stringToSign),
	);
	const sig = bufferToHex(new Uint8Array(mac).buffer);

	// Attach the signature to the URL
	url.searchParams.set("sig", sig);

	return new Response(url);
}

export default {
	async fetch(request, env, ctx) {
		const url = new URL(request.url);
		const imageDeliveryURL = new URL(
			url.pathname
				.slice(1)
				.replace("https:/imagedelivery.net", "https://imagedelivery.net"),
		);
		// IMAGES_SIGNING_KEY is set via `npx wrangler secret put IMAGES_SIGNING_KEY`
		return generateSignedUrl(imageDeliveryURL, env.IMAGES_SIGNING_KEY);
	},
};
const EXPIRATION = 60 * 60 * 24; // 1 day

const bufferToHex = (buffer: ArrayBuffer) =>
	[...new Uint8Array(buffer)]
		.map((x) => x.toString(16).padStart(2, "0"))
		.join("");

async function generateSignedUrl(
	url: URL,
	signingKey: string,
): Promise<Response> {
	// `url` is a full imagedelivery.net URL
	// e.g. https://imagedelivery.net/cheeW4oKsx5ljh8e8BoL2A/bc27a117-9509-446b-8c69-c81bfeac0a01/mobile

	const encoder = new TextEncoder();
	const secretKeyData = encoder.encode(signingKey);
	const key = await crypto.subtle.importKey(
		"raw",
		secretKeyData,
		{ name: "HMAC", hash: "SHA-256" },
		false,
		["sign"],
	);

	// Attach the expiration value to the URL
	const expiry = Math.floor(Date.now() / 1000) + EXPIRATION;
	url.searchParams.set("exp", expiry);
	// `url` now looks like
	// https://imagedelivery.net/cheeW4oKsx5ljh8e8BoL2A/bc27a117-9509-446b-8c69-c81bfeac0a01/mobile?exp=1631289275

	const stringToSign = url.pathname + "?" + url.searchParams.toString();
	// e.g. /cheeW4oKsx5ljh8e8BoL2A/bc27a117-9509-446b-8c69-c81bfeac0a01/mobile?exp=1631289275

	// Generate the HMAC signature
	const mac = await crypto.subtle.sign(
		"HMAC",
		key,
		encoder.encode(stringToSign),
	);
	const sig = bufferToHex(new Uint8Array(mac).buffer);

	// Attach the signature to the URL
	url.searchParams.set("sig", sig);

	return new Response(url);
}

export default {
	async fetch(request, env, ctx): Promise<Response> {
		const url = new URL(request.url);
		const imageDeliveryURL = new URL(
			url.pathname
				.slice(1)
				.replace("https:/imagedelivery.net", "https://imagedelivery.net"),
		);
		// IMAGES_SIGNING_KEY is set via `npx wrangler secret put IMAGES_SIGNING_KEY`
		return generateSignedUrl(imageDeliveryURL, env.IMAGES_SIGNING_KEY);
	},
} satisfies ExportedHandler<Env>;