有没有办法允许使用Access-Control-Allow-Origin
标头的多个跨域?
我知道*
,但它太开放了。我真的想只允许一些域名。
举个例子,像这样:
Access-Control-Allow-Origin: http://domain1.example, http://domain2.example
我已经尝试过上面的代码,但它似乎不适用于Firefox。
是否可以指定多个域,或者我只坚持一个?
听起来像推荐的方法是让服务器从客户端读取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>
对于安装了URL Rewrite 2.0模块的IIS 7.5+,请参阅this SO answer
这是基于yesthatguy的答案的Java Web应用程序解决方案。
我正在使用Jersey 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
应该是唯一的,如果你是CDN(内容交付网络)的背后,Vary
应该设置为Origin
。
我的Nginx配置的相关部分:
if ($http_origin ~* (https?://.*\.mydomain.example(:[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';
}
也许我错了,但据我所知,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并在该文件中添加相同的指令,在<VirtualHost _default_:443>
部分内。
更新配置文件后,您需要在终端中运行以下命令:
a2enmod headers
sudo service apache2 reload
如果您遇到字体问题,请使用:
<FilesMatch "\.(ttf|ttc|otf|eot|woff)$">
<IfModule mod_headers>
Header set Access-Control-Allow-Origin "*"
</IfModule>
</FilesMatch>
对于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();
});
所有浏览器都不使用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>
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
我在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");
}
对于.NET应用程序的相当容易的复制/粘贴,我写了这个来从global.asax文件中启用CORS。此代码遵循当前接受的答案中给出的建议,反映请求中给出的任何原点返回到响应中。这有效地实现了'*'而不使用它。这样做的原因是它支持多种其他CORS功能,包括能够发送带有'withCredentials'属性设置为'true'的AJAX XMLHttpRequest。
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();
}
}
这也允许对OPTIONS
动词进行CORS处理。
用于匹配子域的PHP代码示例。
if( preg_match("/http:\/\/(.*?)\.yourdomain.example/", $_SERVER['HTTP_ORIGIN'], $matches )) {
$theMatch = $matches[0];
header('Access-Control-Allow-Origin: ' . $theMatch);
}
更灵活的方法是使用Apache 2.4的表达式。您可以匹配域,路径以及几乎所有其他请求变量。尽管响应是*
,但是接收响应的唯一请求者仍然是满足要求的请求者。
<IfModule mod_headers.c>
<If "%{HTTP:Host} =~ /\\bcdndomain\\.example$/i && %{HTTP:Origin} =~ /\\bmaindomain\\.example$/i">
Header set Access-Control-Allow-Origin "*"
</If>
</IfModule>
谷歌对serving ads over SSL和grammar in the RFC itself的支持答案似乎表明你可以用空格分隔URL。不确定这在不同浏览器中的支持程度如何。
如果您尝试使用像我这样的代码示例来使其使用CORS,那么值得一提的是,您必须首先清除缓存以尝试它是否真的有效,类似于旧图像仍然存在时的问题,即使它是在服务器上删除(因为它仍保存在缓存中)。
例如,Google Chrome中的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的跨源请求,您需要允许对应用程序的Option请求和在控制器级别下面的注释添加。
[EnableCors(UrlString,Header,Method)]现在原点只能传递一个s字符串。因此,如果要在请求中传递多个URL,请将其作为逗号分隔值传递。
UrlString =“https://a.hello.com,https://b.hello.com”
我们也可以在Asp.net应用程序的Global.asax文件中设置它。
protected void Application_BeginRequest(object sender, EventArgs e)
{
// enable CORS
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "https://www.youtube.com");
}
答案似乎是不止一次使用标题。也就是说,而不是发送
Access-Control-Allow-Origin: http://domain1.example, http://domain2.example, http://domain3.example
发送
Access-Control-Allow-Origin: http://domain1.example
Access-Control-Allow-Origin: http://domain2.example
Access-Control-Allow-Origin: http://domain3.example
在Apache上,您可以使用httpd.conf
和以下语法在<VirtualHost>
.htaccess
部分或mod_headers
文件中执行此操作:
Header add Access-Control-Allow-Origin "http://domain1.example"
Header add Access-Control-Allow-Origin "http://domain2.example"
Header add Access-Control-Allow-Origin "http://domain3.example"
诀窍是使用add
而不是append
作为第一个参数。
这对我有用:
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
中的正则表达式即可。
以下是如果它与您的域与Nginx匹配时如何回显Origin头,如果您想要为多个子域提供字体,这非常有用:
location /fonts {
# this will echo back the origin header
if ($http_origin ~ "example.org$") {
add_header "Access-Control-Allow-Origin" $http_origin;
}
}
这是我为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
头的值,而不是返回*
通配符。
您应该注意一个缺点:只要您将文件外包到CDN(或任何其他不允许编写脚本的服务器)或者您的文件缓存在代理上,就会根据“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>
对于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”标头。