目录
- 基于Referer
- 利用URL签名鉴权
基于Referer
HTTP协议规范在HTTP标头中定义了referer字段(RFC 7231),用于表示HTTP请求来源。该字段值由浏览器在发起HTTP请求时指定,代表当前HTTP请求的来源。例如在点击网页链接时,浏览器会向服务器提交一个HTTP请求,请求中HTTP标头的referer字段值为引用该资源的网页地址,即用户点击的网页地址。
- 使用 AWS Lambda@Edge实现
某些应用场景下,用户需要配置复杂的访问控制规则。Lamba@Edge与CloudFront配合使用,可以有效应对复杂访问策略带来的挑战。Lambda@Edge是AWS利用Lambda无服务器计算服务结合CloudFront内容分发网络,在边缘站点运行Lambda代码,从而在边缘站点实现动态Web应用程序的技术。
由于Lambda@Edge通过编写代码实现对Web请求的精确过滤,因此可以为用户提供更加灵活的过滤条件和数据处理方式。Lambda@Edge功能支持使用Lambda在CloudFront边缘节点对HTTP请求和响应进行按需调整。当CloudFront收到用户请求,CloudFront从源端请求资源,CloudFront接收到源端反馈资源和CloudFront即将向用户返回资源时,均支持调用Lambda对HTTP请求或响应进行按需处理。
Lambda函数内容则如下图所示,用户可以在”handler”函数中编写自定义处理流程,在必要时,可以创建response对象,并将response对象的状态设置为403,从而达到禁止访问的效果。
- 使用 Cloudflare Workers实现
Cloudflare Workers 是 Cloudflare 提供的无服务器服务,用于使无服务器功能尽可能接近最终用户运行。Cloudflare Workers 是根据 Service Workers API 用JavaScript 编写的,这意味着它们可以使用 Service Workers 提供的所有功能。区别在于 Service Workers 在客户端(Web 浏览器)内运行,而 Cloudflare Workers 在边缘服务器上运行。
const ALLOW_LIST = ["www.loongzxl.com"];
// Check requests for referer
const authorizeRefererRequest = (request) => {
const referer = request.headers.get("referer");
if (!referer) {
return false;
}
const url = new URL(referer);
const host = url.host;
return ALLOW_LIST.includes(host);
};
export default {
async fetch(request, env) {
const url = new URL(request.url);
const key = url.pathname.slice(1);
if (!authorizeRefererRequest(request)) {
return new Response("Forbidden", { status: 403 });
}
switch (request.method) {
case "GET":
const object = await env.MY_BUCKET.get(key);
if (object === null) {
return new Response("Object Not Found", { status: 404 });
}
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set("etag", object.httpEtag);
return new Response(object.body, {
headers,
});
default:
return new Response("Method Not Allowed", {
status: 405,
headers: {
Allow: "GET",
},
});
}
},
};
利用URL签名鉴权
使用HTTP标头字段实现防盗链可以应对常见的盗链情形,但盗链者仍然可以通过其它手段去生成一个具有合法HTTP标头的请求,从而获取访问文件的能力。为了进一步提升文件访问的安全性,可以通过对请求的URL添加一个具有时效性的随机验证码作为签名。用户通过签名的地址访问相关资源,系统在后台对签名信息进行比对,确认签名正确性和时效性,从而识别当前请求是否有权访问对应文件。
- 使用 AWS Lambda@Edge实现
AWS CloudFront Signed URL提供一整套签名管理方案,包括签名URL生成API,与CloudFront集成的签名验证机制,从而简化资源访问控制。
客户端在访问CloudFront资源前,需要通过签名URL生成器获取经签名的URL地址。下面签名示例中签名URL主要包含几个要素:被签名的文件访问路径,签名URL生效和失效时间,请求客户端IP地址范围,以及签名URL使用的密钥信息。当CloudFront收到资源请求时,会自动识别URL中签名部分是否正确,是否仍在有效期内,从而确定是否返回对应资源。
- 使用 Cloudflare Workers实现
// How long an token should be valid for, in seconds
const EXPIRY = 5 * 60;
export default {
async fetch(request, env) {
const url = new URL(request.url);
const key = url.pathname.slice(1);
const check = authorizeSignURLRequest(request, env)
if (!check) {
return new Response("Forbidden", { status: 403 });
}
if (check.code != 1) {
return new Response(check.msg, { status: 403 });
}
switch (request.method) {
case "GET":
const object = await env.MY_BUCKET.get(key);
if (object === null) {
return new Response("Object Not Found", { status: 404 });
}
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set("etag", object.httpEtag);
return new Response(object.body, {
headers,
});
default:
return new Response("Method Not Allowed", {
status: 405,
headers: {
Allow: "GET",
},
});
}
},
};
// Check requests for a pre-shared secret
const authorizeSignURLRequest = (request, env) => {
const url = new URL(request.url);
// Make sure you have the necessary query parameters.
const access_key_id = url.searchParams.get('key');
if (!access_key_id) {
return { code: 403, msg: "Invalid parameter:missing key" };
}
const timestamp = url.searchParams.get('timestamp');
if (!timestamp) {
return { code: 403, msg: "Invalid parameter:missing timestamp" };
}
const sign = url.searchParams.get('sign');
if (!sign) {
return { code: 403, msg: "Invalid parameter:missing sign" };
}
const access_key_secret = fetchAccessKeySecret(access_key_id, env);
if (!access_key_secret) {
return { code: 403, msg: "Invalid key" };
}
const assertedTimestamp = Number(timestamp);
// Signed requests expire after five minute. Note that this value should depend on your specific use case
if (Date.now() > assertedTimestamp + EXPIRY * 1000) {
return { code: 403, msg: `URL expired at ${new Date(assertedTimestamp + EXPIRY * 1000)}` };
}
// md5(keysecret+path+timestamp)
const dataToAuthenticate = `${access_key_secret}${url.pathname}${assertedTimestamp}`;
const md5Data = createHash('md5').update(dataToAuthenticate).digest('hex');
const verifiedData = Buffer.from(md5Data).toString("base64");
if (verifiedData != sign) {
return { code: 403, msg: "Invalid sign" };
}
return { code: 1, msg: "Valid url" };
};
// Get access key secret by access key id
const fetchAccessKeySecret = (key, env) => {
var secret;
if (key === env.ACCESS_KEY_ID) {
secret = env.ACCESS_KEY_SECRET;
}
return secret;
}
客户端在访问Cloudflare资源前,需要访问经签名的URL地址,上面示例中签名规则如下:
参考
- [1] 盗链行为与 AWS 防盗链技术
- [2] CDN 常见防盗链技术方案
- [3] 无服务器计算如何提高性能?