有没有办法使用 Access-Control-Allow-Origin
标头允许多个跨域?
我知道 *
,但它太开放了。我真的想只允许几个域。
例如,像这样:
Access-Control-Allow-Origin: http://domain1.example, http://domain2.example
我已经尝试了上面的代码,但它似乎在 Firefox 中不起作用。
是否可以指定多个域,还是我只使用一个域?
Access-Control-Allow-Origin
标头中的 cretain 域不意味着其他域无法触发此端点上的方法(例如 REST API 方法)。这只是意味着不允许的来源不能在 javascript 中使用结果(浏览器确保这一点)。为了限制对特定域的端点的访问,请使用服务器端请求过滤器,例如为不允许的域返回 HTTP 401。
Vary: Origin
标头,请参阅:fetch.spec.whatwg.org/#cors-protocol-and-http-caches
听起来推荐的方法是让您的服务器从客户端读取 Origin 标头,将其与您希望允许的域列表进行比较,如果匹配,则将 Origin
标头的值回显到客户端作为响应中的 Access-Control-Allow-Origin
标头。
使用 .htaccess
,您可以这样做:
# ----------------------------------------------------------------------
# Allow loading of external fonts
# ----------------------------------------------------------------------
<FilesMatch "\.(ttf|otf|eot|woff|woff2)$">
<IfModule mod_headers.c>
SetEnvIf Origin "http(s)?://(www\.)?(google.com|staging.google.com|development.google.com|otherdomain.example|dev02.otherdomain.example)$" AccessControlAllowOrigin=$0
Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
Header merge Vary Origin
</IfModule>
</FilesMatch>
我在 PHP 中使用的另一个解决方案:
$http_origin = $_SERVER['HTTP_ORIGIN'];
if ($http_origin == "http://www.domain1.com" || $http_origin == "http://www.domain2.com" || $http_origin == "http://www.domain3.com")
{
header("Access-Control-Allow-Origin: $http_origin");
}
这对我有用:
SetEnvIf Origin "^http(s)?://(.+\.)?(domain\.example|domain2\.example)$" origin_is=$0
Header always set Access-Control-Allow-Origin %{origin_is}e env=origin_is
当放入 .htaccess
时,它肯定会工作。
我对 woff-fonts 有同样的问题,多个子域必须具有访问权限。为了允许子域,我在 httpd.conf 中添加了类似的内容:
SetEnvIf Origin "^(.*\.example\.com)$" ORIGIN_SUB_DOMAIN=$1
<FilesMatch "\.woff$">
Header set Access-Control-Allow-Origin "%{ORIGIN_SUB_DOMAIN}e" env=ORIGIN_SUB_DOMAIN
</FilesMatch>
对于多个域,您只需更改 SetEnvIf
中的正则表达式。
^(https?:\/\/localhost:\d+)$|^(https?:\/\/.+\.yourdomain\.com)$
下面是实际操作... regex101.com/r/GZHTLB/1 这很疯狂,但 regex101 网站有助于破译这一切。
如果 Origin 标头与您的域与 Nginx 匹配,以下是如何回显 Origin 标头,如果您想为多个子域提供字体,这很有用:
location /fonts {
# this will echo back the origin header
if ($http_origin ~ "example.org$") {
add_header "Access-Control-Allow-Origin" $http_origin;
}
}
对于 ExpressJS 应用程序,您可以使用:
app.use((req, res, next) => {
const corsWhitelist = [
'https://domain1.example',
'https://domain2.example',
'https://domain3.example'
];
if (corsWhitelist.indexOf(req.headers.origin) !== -1) {
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
}
next();
});
这是我为 AJAX 请求的 PHP 应用程序所做的
$request_headers = apache_request_headers();
$http_origin = $request_headers['Origin'];
$allowed_http_origins = array(
"http://myDumbDomain.example" ,
"http://anotherDumbDomain.example" ,
"http://localhost" ,
);
if (in_array($http_origin, $allowed_http_origins)){
@header("Access-Control-Allow-Origin: " . $http_origin);
}
如果我的服务器允许请求来源,则将 $http_origin
本身作为 Access-Control-Allow-Origin
标头的值返回,而不是返回 *
通配符。
$request_headers['Origin'];
是否存在,否则任何直接请求都会触发 E_NOTICE。
您应该注意一个缺点:一旦您将文件外包给 CDN(或任何其他不允许脚本编写的服务器),或者如果您的文件缓存在代理上,则根据“Origin”更改响应请求标头将不起作用。
让 Nginx 用户允许对多个域进行 CORS。我喜欢@marshall 的例子,尽管他的回答只匹配一个域。为了匹配域和子域的列表,这个正则表达式可以轻松使用字体:
location ~* \.(?:ttf|ttc|otf|eot|woff|woff2)$ {
if ( $http_origin ~* (https?://(.+\.)?(domain1|domain2|domain3)\.(?:me|co|com)$) ) {
add_header "Access-Control-Allow-Origin" "$http_origin";
}
}
这只会回显与给定域列表匹配的“Access-Control-Allow-Origin”标头。
对于多个域,在您的 .htaccess
中:
<IfModule mod_headers.c>
SetEnvIf Origin "http(s)?://(www\.)?(domain1.example|domain2.example)$" AccessControlAllowOrigin=$0$1
Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
Header set Access-Control-Allow-Credentials true
</IfModule>
如上所述,如果您位于 CDN(内容分发网络)之后,Access-Control-Allow-Origin
应该是唯一的,并且 Vary
应该设置为 Origin
。
我的 Nginx 配置的相关部分:
if ($http_origin ~* (https?://.*\.mydomain\.com(:[0-9]+)?)) {
set $cors "true";
}
if ($http_origin ~* (https?://.*\.my-other-domain\.com(:[0-9]+)?)) {
set $cors "true";
}
if ($cors = "true") {
add_header 'Access-Control-Allow-Origin' "$http_origin";
add_header 'X-Frame-Options' "ALLOW FROM $http_origin";
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Vary' 'Origin';
}
.
都需要转义,否则它匹配任何字符。此外,您可以只有一个 if 语句而不是使用变量
这是基于 yesthatguy 的答案的 Java Web 应用程序的解决方案。
我正在使用泽西 REST 1.x
配置 web.xml 以了解 Jersey REST 和 CORSResponseFilter
<!-- Jersey REST config -->
<servlet>
<servlet-name>JAX-RS Servlet</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
<param-value>com.your.package.CORSResponseFilter</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.your.package</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>JAX-RS Servlet</servlet-name>
<url-pattern>/ws/*</url-pattern>
</servlet-mapping>
这是 CORSResponseFilter 的代码
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;
public class CORSResponseFilter implements ContainerResponseFilter{
@Override
public ContainerResponse filter(ContainerRequest request,
ContainerResponse response) {
String[] allowDomain = {"http://localhost:9000","https://my.domain.example"};
Set<String> allowedOrigins = new HashSet<String>(Arrays.asList (allowDomain));
String originHeader = request.getHeaderValue("Origin");
if(allowedOrigins.contains(originHeader)) {
response.getHttpHeaders().add("Access-Control-Allow-Origin", originHeader);
response.getHttpHeaders().add("Access-Control-Allow-Headers",
"origin, content-type, accept, authorization");
response.getHttpHeaders().add("Access-Control-Allow-Credentials", "true");
response.getHttpHeaders().add("Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS, HEAD");
}
return response;
}
}
也许我错了,但据我所知 Access-Control-Allow-Origin
有一个 "origin-list"
作为参数。
通过 definition,origin-list
是:
origin = "origin" ":" 1*WSP [ "null" / origin-list ]
origin-list = serialized-origin *( 1*WSP serialized-origin )
serialized-origin = scheme "://" host [ ":" port ]
; <scheme>, <host>, <port> productions from RFC3986
由此,我认为不同的起源是被承认的,应该用空格分开。
我很难为运行 HTTPS 的域进行设置,所以我想我会分享解决方案。我在 httpd.conf 文件中使用了以下指令:
<FilesMatch "\.(ttf|otf|eot|woff)$">
SetEnvIf Origin "^http(s)?://(.+\.)?example\.com$" AccessControlAllowOrigin=$0
Header set Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
</FilesMatch>
将 example.com
更改为您的域名。在您的 httpd.conf 文件中的 <VirtualHost x.x.x.x:xx>
内添加此内容。请注意,如果您的 VirtualHost
有端口后缀(例如 :80
),那么该指令将不适用于 HTTPS,因此您还需要转到 /etc/apache2/sites-available/default-ssl< /em> 并在该文件的 <VirtualHost _default_:443>
部分内添加相同的指令。
更新配置文件后,您将需要在终端中运行以下命令:
a2enmod headers
sudo service apache2 reload
^http(s)?://(.+\.)?example\.com(:\d+)?$
PHP代码:
$httpOrigin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : null;
if (in_array($httpOrigin, [
'http://localhost:9000', // Co-worker dev-server
'http://127.0.0.1:9001', // My dev-server
])) header("Access-Control-Allow-Origin: ${httpOrigin}");
header('Access-Control-Allow-Credentials: true');
如果您遇到字体问题,请使用:
<FilesMatch "\.(ttf|ttc|otf|eot|woff)$">
<IfModule mod_headers>
Header set Access-Control-Allow-Origin "*"
</IfModule>
</FilesMatch>
并非所有浏览器都使用 HTTP_ORIGIN。 How secure is HTTP_ORIGIN? 对我来说,它在 FF 中是空的。
我让允许访问我的网站的网站通过网站 ID 发送,然后我检查我的数据库中具有该 ID 的记录并获取 SITE_URL 列值 ( www.yoursite.com)。
header('Access-Control-Allow-Origin: http://'.$row['SITE_URL']);
即使通过有效的站点 ID 发送请求也需要来自与该站点 ID 关联的我的数据库中列出的域。
这是 apache 的扩展选项,其中包括一些最新和计划的字体定义:
<FilesMatch "\.(ttf|otf|eot|woff|woff2|sfnt|svg)$">
<IfModule mod_headers.c>
SetEnvIf Origin "^http(s)?://(.+\.)?(domainname1|domainname2|domainname3)\.(?:com|net|org)$" AccessControlAllowOrigin=$0$1$2
Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
Header set Access-Control-Allow-Credentials true
</IfModule>
</FilesMatch>
为了对 .NET 应用程序进行相当简单的复制/粘贴,我编写此代码是为了从 global.asax
文件中启用 CORS。此代码遵循当前接受的答案中给出的建议,将请求中给出的任何来源反映到响应中。这有效地实现了'*'而不使用它。
这样做的原因是它启用了多个其他 CORS 功能,包括发送 AJAX XMLHttpRequest 并将“withCredentials”属性设置为“true”的能力。
void Application_BeginRequest(object sender, EventArgs e)
{
if (Request.HttpMethod == "OPTIONS")
{
Response.AddHeader("Access-Control-Allow-Methods", "GET, POST");
Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
Response.AddHeader("Access-Control-Max-Age", "1728000");
Response.End();
}
else
{
Response.AddHeader("Access-Control-Allow-Credentials", "true");
if (Request.Headers["Origin"] != null)
Response.AddHeader("Access-Control-Allow-Origin" , Request.Headers["Origin"]);
else
Response.AddHeader("Access-Control-Allow-Origin" , "*");
}
}
为了方便一个 ASMX 服务的多域访问,我在 global.asax 文件中创建了这个函数:
protected void Application_BeginRequest(object sender, EventArgs e)
{
string CORSServices = "/account.asmx|/account2.asmx";
if (CORSServices.IndexOf(HttpContext.Current.Request.Url.AbsolutePath) > -1)
{
string allowedDomains = "http://xxx.yyy.example|http://aaa.bbb.example";
if(allowedDomains.IndexOf(HttpContext.Current.Request.Headers["Origin"]) > -1)
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", HttpContext.Current.Request.Headers["Origin"]);
if(HttpContext.Current.Request.HttpMethod == "OPTIONS")
HttpContext.Current.Response.End();
}
}
这也允许 CORS 处理 OPTIONS
动词。
用于匹配子域的 PHP 代码示例。
if( preg_match("/http:\/\/(.*?)\.yourdomain.example/", $_SERVER['HTTP_ORIGIN'], $matches )) {
$theMatch = $matches[0];
header('Access-Control-Allow-Origin: ' . $theMatch);
}
AWS Lambda/API 网关
有关如何在无服务器 AWS Lambda 和 API Gateway 上配置多个源的信息——尽管对于人们认为应该非常简单的事情来说,这是一个相当大的解决方案——请参见此处:
https://stackoverflow.com/a/41708323/1624933
目前无法在 API Gateway 中配置多个来源,请参见此处:https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors-console.html),但建议(在上面的答案中)是:
检查浏览器发送的 Origin 标头
对照来源白名单检查它
如果匹配,则将传入的 Origin 作为 Access-Control-Allow-Origin 标头返回,否则返回占位符(默认来源)。
简单的解决方案显然是像这样启用 ALL (*):
exports.handler = async (event) => {
const response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials" : true // Required for cookies, authorization headers with HTTPS
},
body: JSON.stringify([{
但在 API Gateway 端执行此操作可能会更好(请参阅上面的第二个链接)。
Access-Control-Allow-Credentials: true
不允许与通配符 Access-Control-Allow-Origin: *
一起使用。改为设置特定的 <origin>
。
我有 https://stackoverflow.com/a/7454204/13779574 此代码运行良好,但在用户进入该页面时出现错误。我用这段代码解决了这个问题。
if (isset($_SERVER['HTTP_ORIGIN'])) {
$http_origin = $_SERVER['HTTP_ORIGIN'];
if ($http_origin == "http://localhost:3000" || $http_origin == "http://api.loc/"){
header("Access-Control-Allow-Origin: $http_origin");
}
}
在 Django 中还有一个答案。要让一个视图允许来自多个域的 CORS,这是我的代码:
def my_view(request):
if 'HTTP_ORIGIN' in request.META.keys() and request.META['HTTP_ORIGIN'] in ['http://allowed-unsecure-domain.com', 'https://allowed-secure-domain.com', ...]:
response = my_view_response() # Create your desired response data: JsonResponse, HttpResponse...
# Then add CORS headers for access from delivery
response["Access-Control-Allow-Origin"] = request.META['HTTP_ORIGIN']
response["Access-Control-Allow-Methods"] = "GET" # "GET, POST, PUT, DELETE, OPTIONS, HEAD"
response["Access-Control-Max-Age"] = "1000"
response["Access-Control-Allow-Headers"] = "*"
return response
Google 对 serving ads over SSL 和 grammar in the RFC itself 的支持答案似乎表明您可以用空格分隔 URL。不确定在不同的浏览器中对此的支持程度如何。
origin-list
下看到:tools.ietf.org/html/rfc6454#section-7.1
如果您尝试像我这样使用 CORS 使其工作的代码示例如此之多,那么值得一提的是,您必须先清除缓存以尝试它是否确实有效,类似于旧图像仍然存在时的问题,即使它是在服务器上删除(因为它仍然保存在您的缓存中)。
例如谷歌浏览器中的 CTRL + SHIFT + DEL 删除您的缓存。
这有助于我在尝试了许多纯 .htaccess
解决方案后使用此代码,这似乎是唯一一个有效的解决方案(至少对我而言):
Header add Access-Control-Allow-Origin "http://google.com"
Header add Access-Control-Allow-Headers "authorization, origin, user-token, x-requested-with, content-type"
Header add Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTIONS"
<FilesMatch "\.(ttf|otf|eot|woff)$">
<IfModule mod_headers.c>
SetEnvIf Origin "http(s)?://(www\.)?(google.com|staging.google.com|development.google.com|otherdomain.com|dev02.otherdomain.net)$" AccessControlAllowOrigin=$0
Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
</IfModule>
</FilesMatch>
另请注意,许多解决方案都说您必须输入 Header set ...
但它是 Header add ...
的情况很普遍。希望这可以帮助像我这样在几个小时内遇到同样麻烦的人。
下面的答案特定于 C#,但这个概念应该适用于所有不同的平台。
要允许来自 web api 的跨域请求,您需要允许对您的应用程序的选项请求,并在控制器级别添加以下注释。
[EnableCors(UrlString,Header, Method)] 现在只能将来源作为字符串传递。因此,如果您想在请求中传递多个 URL,请将其作为逗号分隔值传递。
网址字符串 = "https://a.hello.com,https://b.hello.com"
只能为 Access-Control-Allow-Origin 标头指定一个来源。但是您可以根据请求在响应中设置来源。也不要忘记设置 Vary 标头。在 PHP 中,我会执行以下操作:
/**
* Enable CORS for the passed origins.
* Adds the Access-Control-Allow-Origin header to the response with the origin that matched the one in the request.
* @param array $origins
* @return string|null returns the matched origin or null
*/
function allowOrigins($origins)
{
$val = $_SERVER['HTTP_ORIGIN'] ?? null;
if (in_array($val, $origins, true)) {
header('Access-Control-Allow-Origin: '.$val);
header('Vary: Origin');
return $val;
}
return null;
}
if (allowOrigins(['http://localhost', 'https://localhost'])) {
echo your response here, e.g. token
}
我也面临同样的问题。我的客户端在 9097 上,api 网关在 9098 上,微服务在....实际上我在我的网关 yml 文件中使用了 spring cloud Api 网关,我允许跨域之类的——... allowedOrigins:“http://localhost:9097 "
在我的微服务中我也在使用@crossOrigin
当客户端向 api 网关发送请求时,响应中出现了两个“Access-Control-Allow-Origin”标头 [一个来自 api yml 文件,一个来自微服务 @crossorigin],因此浏览器阻止了请求
我解决了它 -
@Bean
public RouteLocator getRL(RouteLocatorBuilder builder) {
return builder.routes()
.route(p->
"/friendlist","/guest/**"
)
.filters(f ->{
//f.removeResponseHeader("Access-Control-Allow-Origin");
//f.addResponseHeader("Access-Control-Allow-Origin","http://localhost:9097");
f.setResponseHeader("Access-Control-Allow-Origin","http://localhost:9097");
return f;
})
.uri("lb://OLD-SERVICE")
).build();
}
不定期副业成功案例分享
*.example.com
或通配符端口,例如:localhost:*
(.+\.google.com)
而不是(google.com|staging.google.com)
的人Access-Control-Allow-Origin
的输出是什么?http
方案),并且应转义 DNS 标签分隔符(\.
而不是.
);否则,攻击者可以购买developmentzgoogle.com
域并从那里发起跨域攻击。