AMP 电子邮件中的 CORS
跨域资源共享 (CORS) 是一种机制,它使用 HTTP 标头来告知浏览器哪些来源可以使用 XHR 访问资源。AMP 电子邮件通过添加 HTTP 标头来扩展此机制,这些标头类似地告知电子邮件客户端哪些发送者被允许访问这些资源。
目前此机制有两个版本。目前,建议您在服务器端支持这两个版本。
版本 2
当电子邮件通过 amp-form
、amp-list
或任何其他基于 XHR 的机制发出请求时,电子邮件客户端会包含以下 HTTP 标头
AMP-Email-Sender
,设置为电子邮件发送者的电子邮件地址。
它期望 HTTP 响应返回包含以下标头
AMP-Email-Allow-Sender
,其值与请求中的AMP-Email-Sender
相同,或者为*
,表示允许所有发送者电子邮件。
示例
用户通过访问 https://emailclient.example/myinbox
在其电子邮件客户端中打开来自 sender@company.example
的电子邮件。该电子邮件使用 AMP 电子邮件,并使用来自 https://company.example/data.json
的 amp-list
加载数据。
电子邮件客户端向 https://company.example/data.json
发送 HTTP 请求,并设置以下标头
AMP-Email-Sender: sender@company.example
电子邮件客户端期望 HTTP 响应包含以下标头
AMP-Email-Allow-Sender: sender@company.example
或者,允许在任一标头中使用 *
(但不建议)。
版本 1(已弃用)
在版本 1 中,电子邮件客户端使用查询字符串参数而不是 HTTP 标头来指示发送者电子邮件。它还提供一个 Origin
标头,并且需要在响应中提供 Access-Control-Allow-Origin
标头,就像网站上的 CORS 一样。
当电子邮件通过 amp-form
、amp-list
或任何其他基于 XHR 的机制发出请求时,电子邮件客户端会包含以下 HTTP 标头
Origin
,其值为用于显示电子邮件的页面的来源。
URL 也始终有一个查询字符串,其中 __amp_source_origin
参数设置为电子邮件发送者的电子邮件地址。
它期望 HTTP 响应包含以下标头
Access-Control-Allow-Origin
,其值与请求中的Origin
相同AMP-Access-Control-Allow-Source-Origin
,其值与请求中的__amp_source_origin
查询字符串参数相同。Access-Control-Expose-Headers
设置为AMP-Access-Control-Allow-Source-Origin
示例
用户通过访问 https://emailclient.example/myinbox
在其电子邮件客户端中打开来自 sender@company.example
的电子邮件。该电子邮件使用 AMP 电子邮件,并使用来自 https://company.example/data.json
的 amp-list
加载数据。
电子邮件客户端向 https://company.example/data.json?__amp_source_origin=sender@company.example
发送 HTTP 请求(请注意添加的查询字符串),并设置以下标头
Origin: https://emailclient.example
电子邮件客户端期望 HTTP 响应包含以下标头
Access-Control-Allow-Origin: https://emailclient.example
AMP-Access-Control-Allow-Source-Origin: sender@company.example
Access-Control-Expose-Headers: AMP-Access-Control-Allow-Source-Origin
请注意,此版本不支持在 AMP-Access-Control-Allow-Source-Origin
标头中使用 *
。
实现 CORS
以下是在服务器端采取的实现 CORS 以支持版本 1 和 2 的推荐步骤
当您收到 HTTP 请求时,请检查是否设置了 Origin
和 AMP-Email-Sender
HTTP 标头。
- 如果设置了
AMP-Email-Sender
标头- 让 *senderEmail* 为
AMP-Email-Sender
标头的值。 - 检查 *senderEmail* 是否是您拥有的或您信任的电子邮件地址。如果不是,则拒绝该请求。
- 将响应标头
AMP-Email-Allow-Sender
设置为 *senderEmail*。
- 让 *senderEmail* 为
- 如果设置了
Origin
标头,但未设置AMP-Email-Sender
- 让 *requestOrigin* 为
Origin
标头的值。 - 将响应标头
Access-Control-Allow-Origin
设置为 *requestOrigin*。 - 检查 URL 是否包含
__amp_source_origin
查询字符串参数。如果没有,则拒绝该请求。 - 让 *senderEmail* 为
__amp_source_origin
查询字符串参数的值。 - 检查 *senderEmail* 是否是您拥有的或您信任的电子邮件地址。如果不是,则拒绝该请求。
- 将响应标头
AMP-Access-Control-Allow-Source-Origin
设置为 *senderEmail*。 - 将响应标头
Access-Control-Expose-Headers
设置为AMP-Access-Control-Allow-Source-Origin
。
- 让 *requestOrigin* 为
- 如果未设置
Origin
和AMP-Email-Sender
,则拒绝该请求。
示例 1
电子邮件客户端发送的请求
GET /data.json?__amp_source_origin=sender@company.example HTTP/1.1
Host: company.example
Origin: https://emailclient.example
User-Agent: EmailClientProxy
Accept: application/json
预期的响应标头
Access-Control-Allow-Origin: https://emailclient.example
Access-Control-Expose-Headers: AMP-Access-Control-Allow-Source-Origin
AMP-Access-Control-Allow-Source-Origin: sender@company.example
解释
由于设置了 Origin
标头,因此此请求正在使用 CORS 版本 1,并且需要设置上面列出的三个标头。
示例 2
电子邮件客户端发送的请求
GET /data.json HTTP/1.1
Host: company.example
AMP-Email-Sender: sender@company.example
User-Agent: EmailClientProxy
Accept: application/json
预期的响应标头
AMP-Email-Allow-Sender: sender@company.example
解释
由于设置了 AMP-Email-Sender
标头,因此此请求正在使用 CORS 版本 2,并且只需要 AMP-Email-Allow-Sender
标头。
示例 3
电子邮件客户端发送的请求
GET /data.json?__amp_source_origin=sender@company.example HTTP/1.1
Host: company.example
Origin: https://emailclient.example
AMP-Email-Sender: sender@company.example
User-Agent: EmailClientProxy
Accept: application/json
预期的响应标头
AMP-Email-Allow-Sender: sender@company.example
解释
同时设置了 Origin
和 AMP-Email-Sender
,表示客户端支持两个版本。由于版本 2 优先,因此仅设置 AMP-Email-Allow-Sender
标头,并且可以安全地忽略 Origin
和 __amp_source_origin
的值。
代码示例
PHP
if (isset($_SERVER['HTTP_AMP_EMAIL_SENDER'])) {
$senderEmail = $_SERVER['HTTP_AMP_EMAIL_SENDER'];
if (!isAllowedSender($senderEmail)) {
die('invalid sender');
}
header("AMP-Email-Allow-Sender: $senderEmail");
} elseif (isset($_SERVER['HTTP_ORIGIN'])) {
$requestOrigin = $_SERVER['HTTP_ORIGIN'];
if (empty($_GET['__amp_source_origin'])) {
die('invalid request');
}
$senderEmail = $_GET['__amp_source_origin'];
if (!isAllowedSender($senderEmail)) {
die('invalid sender');
}
header("Access-Control-Allow-Origin: $requestOrigin");
header('Access-Control-Expose-Headers: AMP-Access-Control-Allow-Source-Origin');
header("AMP-Access-Control-Allow-Source-Origin: $senderEmail");
} else {
die('invalid request');
}
Python (Django)
response = JsonResponse(...)
if request.META.HTTP_AMP_EMAIL_SENDER:
senderEmail = request.META.HTTP_AMP_EMAIL_SENDER
if not isAllowedSender(senderEmail):
raise PermissionDenied
response['AMP-Email-Allow-Sender'] = senderEmail
elif request.META.HTTP_ORIGIN:
requestOrigin = request.META.HTTP_ORIGIN
senderEmail = request.GET.get('__amp_source_origin')
if not isAllowedSender(senderEmail):
raise PermissionDenied
response['Access-Control-Allow-Origin'] = requestOrigin
response['Access-Control-Expose-Headers'] = 'AMP-Access-Control-Allow-Source-Origin'
response['AMP-Access-Control-Allow-Source-Origin'] = senderEmail
else
raise PermissionDenied
SSJS
<script runat="server" language="JavaScript">
Platform.Load("core", "1");
if (Platform.Request.GetRequestHeader("AMP-Email-Sender")) {
var senderEmail = Platform.Request.GetRequestHeader("AMP-Email-Sender")
if (isValidSender(senderEmail)) {
HTTPHeader.SetValue("AMP-Email-Allow-Sender", senderEmail)
} else {
Platform.Function.RaiseError("Sender Not Allowed",true,"statusCode","3");
}
} else if (Platform.Request.GetRequestHeader("Origin")) {
var requestOrigin = Platform.Request.GetRequestHeader("Origin")
if (Platform.Request.GetQueryStringParameter("__amp_source_origin")) {
var senderEmail = Platform.Request.GetQueryStringParameter("__amp_source_origin");
if (isValidSender(senderEmail)) {
HTTPHeader.SetValue("Access-Control-Allow-Origin", requestOrigin);
HTTPHeader.SetValue("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin");
HTTPHeader.SetValue("AMP-Access-Control-Allow-Source-Origin", senderEmail);
} else {
Platform.Function.RaiseError("Invalid Source Origin",true,"statusCode","3");
}
} else {
Platform.Function.RaiseError("Source Origin Not Present",true,"statusCode","3");
}
} else {
Platform.Function.RaiseError("Origin and Sender Not Present",true,"statusCode","3");
}
</script>
访问 Salesforce 开发人员文档以了解有关 SSJS 的更多信息。
Node.js
使用官方支持的 @ampproject/toolbox-cors npm 包。
-
由 @fstanis 编写