赞
踩
目前,各大主流厂商都推出了自己的边缘 Serverless 服务,如 CloudFlare Workers、 Vercel EdgeRuntime 等;腾讯云 EdgeOne 边缘函数提供了部署在边缘节点的 Serverless 代码执行环境,只需编写业务函数代码并设置触发规则,即可在靠近用户的边缘节点上弹性、安全地运行代码。
阅读本文之前,请先了解以下背景知识:
1. EdgeOne 边缘函数
基于边缘函数的访问控制方案自定义程度高、安全性强,在保证加速效果的同时,最大程度保护资源的安全性,同时还可以降低带宽和存储费用,有效节约业务成本。
多数情况下,通过 CDN 分发的内容默认为公开资源,任何拿到 URL 的用户都可以进行访问。但是有时候一些需要保护的资源也会使用 CDN 进行加速,这时,在 CDN 层进行访问控制就显得十分必要:
保护内容:对于网站的付费内容,需要进行保护以防止未授权的用户访问;
防止盗链:有些网站可能会直接使用他人的加速链接来提供内容,以此规避存储和带宽的费用。对资源进行访问控制,可以有效防止"盗链"行为的发生;
节省成本:如果内容被大量的未授权用户访问,带宽和存储费用可能会大幅度增加。通过访问控制,可以限制访问量,从而有效节省成本;
提高安全性:通过防止恶意用户对网站进行攻击,CDN 访问控制可以提高网站的整体安全性。
总的来说,CDN 访问控制可以对特定资源进行保护,因此多数 CDN 厂商都提供了访问控制的功能。
但是,目前大多数 CDN 厂商提供的访问控制能力都相对简单,且不可定制,因此不能完全满足复杂的业务场景。对那些有较高安全需求的客户来说,这些通用的访问控制方案显得有些 “鸡肋”,不得不在源站服务或网关层实现自己的访问控制。
一站式边缘安全加速平台 EdgeOne 被称为 下一代 CDN,其中,EdgeOne 边缘函数 提供了边缘代码执行环境,支持开发者编写、部署 JavaScript 代码。
得益于 EdgeOne 边缘函数的可编程能力,使得 定制 专属的边缘访问控制方案成为可能,更便捷、更灵活、更安全:
访问控制在 EdgeOne 边缘节点完成,无需回源,最大程度 保护内容、节省资源、保护源站:
访问控制效果:
常见的访问控制有 远程鉴权 、 UA 黑白名单 、 Referer 防盗链 等,相当一部分 CDN 使用者有基于这些常规访问控制方案进一步定制的需求,例如:远程鉴权时,添加或修改请求头等。
使用 EdgeOne 边缘函数可以很方便的实现这些方案,并提供进一步定制的能力。
1. 代码示例旨在展示边缘函数的能力,仅列举常见方案的基本实现;
2. 文章中涉及的代码可在 边缘函数最佳实践仓库 查看;
远程鉴权是指将用户请求转发至指定的鉴权服务器,由鉴权服务器对用户请求进行校验。校验通过即允许访问,校验失败拒绝访问或进行其他限制,有效避免资源被非授权用户访问。
使用边缘函数实现远程鉴权:
- const AUTH_URL = "...AUTH_URL...";
-
- async function handleRequest(request) {
- const checkAuthRes = await fetch(AUTH_URL, {
- // 可自定义头部
- headers: request.headers,
- });
- if (checkAuthRes.status === 200) {
- return await fetch(request);
- }
- return new Response(null, {
- status: checkAuthRes.status,
- });
- }
-
- addEventListener("fetch", (e) => {
- e.respondWith(handleRequest(e.request));
- });
限制携带特定 User-Agent 的请求才能访问内容,可以有效防止恶意爬虫或者恶意软件的访问。同时可以根据不同 UA 头部提供定制化内容,例如:只允许移动设备或者特定版本的浏览器访问。
- const ALLOWED_UA = ["Mobile/15E148"];
-
- async function handleRequest(request) {
- const userAgent = request.headers.get("User-Agent") || "";
-
- if (ALLOWED_UA.some((allowedUA) => userAgent.includes(allowedUA))) {
- return await fetch(request);
- } else {
- return new Response(null, { status: 403 });
- }
- }
-
- addEventListener("fetch", (event) => {
- event.respondWith(handleRequest(event.request));
- });
防盗链技术主要用于防止其他网站未经许可地使用你的网站资源,如图片、视频、文档等。
检查 HTTP 请求头中的 Referer 字段是最常见的防盗链方法。当浏览器发送请求获取网页上的资源时,它会在请求头中带上 Referer 字段,该字段指示了请求是从哪个页面发出的。通过检查 Referer 字段,服务器可以确定请求是否来自合法的源。
使用边缘函数实现 Referer 防盗链:
- const RE_URL = "...URL...";
-
- async function handleRequest(request) {
- const referer = request.headers.get("Referer");
-
- if (!referer) {
- return Response.redirect(RE_URL);
- }
-
- const urlInfo = new URL(request.url);
- const isHostMatch = new RegExp(`^https?:\/\/${urlInfo.hostname}`).test(
- referer
- );
-
- if (!isHostMatch) {
- return Response.redirect(RE_URL);
- }
-
- return await fetch(request);
- }
-
- addEventListener("fetch", (event) => {
- event.respondWith(handleRequest(event.request));
- });
URL 鉴权通过校验 URL 中的加密串和时间戳的方式,达到保护源站资源的目的。URL 鉴权方式有 Type A/B/C/D 四种,主要区别是签名计算方式和签名存放位置不同。
使用边缘函数实现 Type A 鉴权:
- const SECRET_KEY = "YOUR_SECRET_KEY";
-
- async function handleRequest(request) {
- const url = new URL(request.url);
- const params = url.searchParams;
- const authKey = params.get("auth_key");
-
- if (!authKey) {
- return new Response(null, { status: 401 });
- }
-
- const [timestamp, nonce, signature] = authKey.split("-");
- const now = Math.floor(Date.now() / 1000);
-
- if (now > timestamp) {
- return new Response(null, { status: 401 });
- }
-
- const encoder = new TextEncoder();
- const data = encoder.encode(
- `${url.pathname}-${timestamp}-${nonce}-${SECRET_KEY}`
- );
- const hashBuffer = await crypto.subtle.digest("MD5", data);
- const hashArray = Array.from(new Uint8Array(hashBuffer));
- const expectedSignature = hashArray
- .map((b) => b.toString(16).padStart(2, "0"))
- .join("");
-
- if (signature !== expectedSignature) {
- return new Response(null, { status: 401 });
- }
-
- return await fetch(request);
- }
-
- addEventListener("fetch", (event) => {
- event.respondWith(handleRequest(event.request));
- });
服务端生成的鉴权 URL 为: .../TencentCloud.svg?auth_key=1698224616-0-311b8888ccc20768895f1f3bd82dbb76
客户端发起请求:https://.../TencentCloud.svg?auth_key=1698224616-0-311b8888ccc20768895f1f3bd82dbb76
触发边缘函数执行,对 auth_key
进行校验,签名错误或过期都会被阻止访问:
- Google Cloud Signed-Cookies
- AmazonCloudFront Private-Content Signed-Cookies
- Cloudflare-One authorization-cookie
使用 EdgeOne 边缘函数不仅可以定制常规的访问控制方案,还可以部署更加复杂的专属方案,以 Signed Cookies 方案为例进行演示。
Signed Cookies 方案采用非对称加密的方式进行访问控制(私钥签名、公钥验签),该方案允许为多个文件提供安全的访问权限,而不需要更改每个 URL。
Signed Cookies 的工作原理是:当用户请求访问受保护的内容前,应用程序或网站会使用私钥生成一组特殊的 HTTP Cookies(EO-Signature
、EO-Policy
),这些 Cookies 包含访问权限的信息,且被加密并签名。随后,这些 Cookies 被发送到用户的浏览器,用户的浏览器在后续的请求中会自动发送这些 Cookies。
当边缘函数处理一个带有 Signed Cookies 的请求时,会验证这些 Cookies 的签名,然后根据 Cookies 中的信息决定是否允许访问请求的内容。
EO-Signature
、EO-Policy
在源站生成,EO-Policy
表示访问控制规则,由服务端对 policy.json
数据进行 url-safe base64 编码生成,policy.json
数据形式为:
- {
- "Statement": [
- {
- "Resource": "/TencentCloud.svg",
- "Condition": {
- "DateGreaterThan": {
- "EpochTime": 1698318316
- },
- "DateLessThan": {
- "EpochTime": 1698404716
- }
- }
- }
- ]
- }
EO-Signature
存储签名信息,由服务端对 policy.json
数据使用私钥进行签名,然后进行 url-safe base64 编码生成,EO-Signature
、EO-Policy
种植后,站点上的受限制资源请求都会携带这两个特殊的 HTTP Cookies,形如:
EO-Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiL1RlbmNlbnRDbG91ZC5zdmciLCJDb25kaXRpb24iOnsiRGF0ZUdyZWF0ZXJUaGFuIjp7IkVwb2NoVGltZSI6MTY5ODMxODMxNn0sIkRhdGVMZXNzVGhhbiI6eyJFcG9jaFRpbWUiOjE2OTg0MDQ3MTZ9fX1dfQ__; Path=/;
EO-Signature=HddK~ZBb2Rc7C3Vpat2QnD2rMqnGAjktNbJ9jON2sh1r0o3YX3ohRkvDowdp2PkhBx1erUQbyiL2T6gSW-E5vbYQfrOPC2aA-7a9ZtnkImDZlk-BVfEL1cZgq3bjsG9FxIy-kc0B5~JgHtQvRsecEyie0yRyZJZoiMoWxN~haP3ykimFa4kl49eIwtOxCnXTvclHea4Rb1jp7iqTz2W-LevjIeSsqe9dL2qehMSamy-wbOCUTZBSqeoB7dS1hMH4SMSIaBUieBFqs-yrDCtA-E4nNd6FjHoMQi4DJcIkSJiwZl371OJdYyRDX65pWHaUHwbr3GrC4XgpyGWwvMhmaw__; Path=/;
使用边缘函数在边缘节点上对 Signed Cookies 进行校验:
- // RSA 公钥
- const PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
- ...
- -----END PUBLIC KEY-----`;
-
- ...
-
- async function verifySignature(request) {
- ...
- const eoSignature = getCookieValue(cookies, 'EO-Signature');
- const eoPolicy = getCookieValue(cookies, 'EO-Policy');
-
- ...
-
- const signatureBuffer = getSignatureBuff(eoSignature);
-
- const policyString = getPolicyStr(eoPolicy);
- const policyBuffer = stringToArrayBuffer(policyString);
-
- const publicKeyBuffer = pemToArrayBuffer(PUBLIC_KEY_PEM);
-
- const publicKey = await crypto.subtle.importKey(
- 'spki',
- publicKeyBuffer,
- {
- name: 'RSASSA-PKCS1-v1_5',
- hash: 'SHA-1',
- },
- true,
- ['verify'],
- );
-
- const signatureValid = await crypto.subtle.verify('RSASSA-PKCS1-v1_5', publicKey, signatureBuffer, policyBuffer);
-
- if (!signatureValid) {
- return null;
- }
-
- return JSON.parse(policyString);
- }
-
- function verifyPolicy(request, policy) {
- const { pathname } = new URL(request.url);
- const { Statement } = policy;
-
- const now = Math.floor(Date.now() / 1000);
-
- for (const statement of Statement) {
- if (statement.Resource === pathname) {
- const dateGreaterThan = statement.Condition.DateGreaterThan.EpochTime;
- const dateLessThan = statement.Condition.DateLessThan.EpochTime;
-
- if (now > dateGreaterThan && now < dateLessThan) {
- return true;
- }
- return false;
- }
- }
-
- return true;
- }
-
- async function handleEvent(event) {
- const { request } = event;
-
- const policy = await verifySignature(request);
- if (!policy) {
- return event.respondWith(new Response(null, { status: 401 }));
- }
- const valid = verifyPolicy(request, policy);
- if (!valid) {
- return event.respondWith(new Response(null, { status: 403 }));
- }
-
- event.respondWith(await fetch(request));
- }
-
- addEventListener('fetch', handleEvent);
Signed Cookies 校验不通过,资源访问会被 Block:
EdgeOne 边缘函数拥有 分布式部署、贴近用户、超低延迟、弹性扩容、Serverless 环境 等优势。
借助 EdgeOne 边缘函数,开发者可以在边缘节点定制访问控制方案,满足更复杂的业务需求,对自己的资源进行保护,降低带宽和存储费用,有效节约成本。
参考 API 列表
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。