Firefox 3.5实现了W3C访问控制规范。因此,Firefox 3.5发送特定的HTTP头用来以内部XMLHttpRequest发送跨站点请求(在Firefox 3.5及以后可以用于调用不同的域)和跨站点字体下载。它还希望获取特定的跨站响应HTTP头信息。这些头信息的概述,包括示例JavaScript代码发起请求和处理来自服务器的响应,以及每个头的讨论,可以在这里找到(HTTP访问控制)。HTTP访问控制文章是很好的使用指南。本文介绍利用PHP处理访问控制请求和制定访问控制响应。本文的目标读者是服务器程序员或管理员。虽然在PHP代码示例所示,类似的概念适用于ASP.net,Perl、Python、Java等;一般来说,这些概念可以应用于任何服务器端编程环境处理HTTP请求和动态制定的HTTP响应。虽然在PHP代码示例所示,类似的概念适用于ASP.net,Perl、Python、Java等;一般来说,这些概念可以应用于任何可以处理HTTP请求和动态制定HTTP响应的服务器端编程环境。
HTTP 头部小议
了解HTTP 头部信息, 建议先阅读这篇文章 covering the HTTP headers used by both clients (such as Firefox 3.5 and beyond) and servers
示例代码
随后的章节中PHP代码(和JavaScript调用服务器)可查看相关代码,这些代码在实现了XMLHttpRequest的浏览器上都可运行,像Firefox 3.5及以上。
简单的跨域请求
简单的访问控制请求 在下列情况下会被发起:
- 请求方式为 HTTP/1.1
GET
或者POST
,如果是POST
,则请求的Content-Type为以下之一:application/x-www-form-urlencoded
,multipart/form-data
, 或text/plain
- 在请求中,不会发送自定义的头部(如X-Modified)
以下情况,请求会返回相关响应信息
- 如果资源是允许公开访问的(就像任何允许GET访问的 HTTP资源),返回Access-Control-Allow-Origin:*头信息就足够了,除非是一些需要Cookies和HTTP身份验证信息的请求。
-
如果资源访问被限制基于相同的域名,或者如果要访问的资源需要凭证(或设置凭证),那么就有必要对请求头信息中的ORIGIN进行过滤,或者至少响应请求的来源(例如Access-Control-Allow-Origin:https://arunranga.com)。另外,将发送Access-Control-Allow-Credentials:TRUE头信息,这在后续部分将进行讨论。
简单的访问控制请求 介绍了在客户端和服务端进行信息交换的HEADER. 下面是一段用来处理简单请求的PHP代码。
<?php // 我们将只授予 arunranga.com 域的访问权限,因为我们认为它通过 application/xml 方式来访问这些资源是安全的。 if($_SERVER['HTTP_ORIGIN'] == "https://arunranga.com") { header('Access-Control-Allow-Origin: https://arunranga.com'); header('Content-type: application/xml'); readfile('arunerDotNetResource.xml'); } else { header('Content-Type: text/html'); echo "<html>"; echo "<head>"; echo " <title>Another Resource</title>"; echo "</head>"; echo "<body>", "<p>This resource behaves two-fold:"; echo "<ul>", "<li>If accessed from <code>https://arunranga.com</code> it returns an XML document</li>"; echo " <li>If accessed from any other origin including from simply typing in the URL into the browser's address bar,"; echo "you get this HTML document</li>", "</ul>", "</body>", "</html>"; } ?>
上面的代码通过检查浏览器发送的 ORIGIN
头部信息(通过 $_SERVER['HTTP_ORIGIN']
) 是否匹配 'https://arunranga.com' 得知,如果是,返回 Access-Control-Allow-Origin: https://arunranga.com 。如果你的浏览器支持访问控制,你可以访问 这里 .
预请求
预请求 发生在下列情况中:
- 使用GET或POST以外的方法;利用POST发送
application/x-www-form-urlencoded
,multipart/form-data
, ortext/plain
之外的Content-Type;例如,post body的Content-type为application/xml
- 发送自定义的头信息,如x-pingaruner
预请求访问控制 这篇文章介绍了在客户端和服务器间进行交换的头信息,响应preflight requests请求的服务器资源会有这些动作:
- 基于
ORIGIN
进行过滤 - preflight请求的响应内容,包括必要的
Access-Control-Allow-Methods
,Access-Control-Allow-Headers
(保证系统正常运行),如果需要凭据的话,也会包括Access-Control-Allow-Credentials
头信息 - 响应实际请求,包括处理 POST数据等。
下面是相关的PHP内容, preflighted request:
<?php if($_SERVER['REQUEST_METHOD'] == "GET") { header('Content-Type: text/plain'); echo "This HTTP resource is designed to handle POSTed XML input from arunranga.com and not be retrieved with GET"; } elseif($_SERVER['REQUEST_METHOD'] == "OPTIONS") { // 告诉客户端我们支持来自 arunranga.com 的请求并且预请求有效期将仅有20天 if($_SERVER['HTTP_ORIGIN'] == "https://arunranga.com") { header('Access-Control-Allow-Origin: https://arunranga.com'); header('Access-Control-Allow-Methods: POST, GET, OPTIONS'); header('Access-Control-Allow-Headers: X-PINGARUNER'); header('Access-Control-Max-Age: 1728000'); header("Content-Length: 0"); header("Content-Type: text/plain"); //exit(0); } else { header("HTTP/1.1 403 Access Forbidden"); header("Content-Type: text/plain"); echo "You cannot repeat this request"; } } elseif($_SERVER['REQUEST_METHOD'] == "POST") { /* 通过首先获得XML传送过来的blob来处理POST请求,然后做一些处理, 最后将结果返回客户端 */ if($_SERVER['HTTP_ORIGIN'] == "https://arunranga.com") { $postData = file_get_contents('php://input'); $document = simplexml_load_string($postData); // 对POST过来的数据进行一些处理 $ping = $_SERVER['HTTP_X_PINGARUNER']; header('Access-Control-Allow-Origin: https://arunranga.com'); header('Content-Type: text/plain'); echo // 处理之后的一些响应 } else die("POSTing Only Allowed from arunranga.com"); } else die("No Other Methods Allowed"); ?>
可以看到,就像POST一样,针对OPTIONS preflight请求,同样返回对应的头信息。这样 以来,处理preflight就像处理普通的request请求一样,在针对OPTIONS请求的响应信息中,服务器通过客户端,实际的请求可以用POST的形式发送,同时可附加X-PINGARUNERP这样的头信息。如果浏览器支持的话,可访问 这里
带凭据的请求
带凭据的请求,将Cookies和HTTP认证信息一起发送出去的跨域请求,根据请求方式,可以是 Simple 或 Preflighted,
发送 简单请求 时, Firefox 3.5 (或以上)会发送带Cookies信息的请求, (如果withCredentials
设以true). 如果服务器响应真的是可信任的, 客户端接受并进行输出。 在 预请求 中,服务器可以针对 OPTIONS
请求,返回 Access-Control-Allow-Credentials: true
信息
下面是处理请求的PHP内容:
<?php if($_SERVER['REQUEST_METHOD'] == "GET") { // First See if There Is a Cookie //$pageAccess = $_COOKIE['pageAccess']; if (!isset($_COOKIE["pageAccess"])) { setcookie("pageAccess", 1, time()+2592000); header('Access-Control-Allow-Origin: https://arunranga.com'); header('Cache-Control: no-cache'); header('Pragma: no-cache'); header('Access-Control-Allow-Credentials: true'); header('Content-Type: text/plain'); echo 'I do not know you or anyone like you so I am going to mark you with a Cookie :-)'; } else { $accesses = $_COOKIE['pageAccess']; setcookie('pageAccess', ++$accesses, time()+2592000); header('Access-Control-Allow-Origin: https://arunranga.com'); header('Access-Control-Allow-Credentials: true'); header('Cache-Control: no-cache'); header('Pragma: no-cache'); header('Content-Type: text/plain'); echo 'Hello -- I know you or something a lot like you! You have been to ', $_SERVER['SERVER_NAME'], ' at least ', $accesses-1, ' time(s) before!'; } } elseif($_SERVER['REQUEST_METHOD'] == "OPTIONS") { // Tell the Client this preflight holds good for only 20 days if($_SERVER['HTTP_ORIGIN'] == "https://arunranga.com") { header('Access-Control-Allow-Origin: https://arunranga.com'); header('Access-Control-Allow-Methods: GET, OPTIONS'); header('Access-Control-Allow-Credentials: true'); header('Access-Control-Max-Age: 1728000'); header("Content-Length: 0"); header("Content-Type: text/plain"); //exit(0); } else { header("HTTP/1.1 403 Access Forbidden"); header("Content-Type: text/plain"); echo "You cannot repeat this request"; } } else die("This HTTP Resource can ONLY be accessed with GET or OPTIONS"); ?>
需要注意的是,在带凭据请求中, Access-Control-Allow-Origin:
头不能是通配符 "*",必须是一个有效的域名。 可参考这里 running here
Apache 示例
设置对某些URI的访问权限
最有效的方法之一,利用Apache rewrite, 环境变量,还有headers使Access-Control-Allow-*
对某些特定的URI生效,比如,以无认证信息形式利用GET跨域请求api(.*).json。
RewriteRule ^/api(.*)\.json$ /api$1.json [CORS=True] Header set Access-Control-Allow-Origin "*" env=CORS Header set Access-Control-Allow-Methods "GET" env=CORS Header set Access-Control-Allow-Credentials "false" env=CORS