Server-Side Access Control
浏览器发送特定的HTTP标头,用于从内部XMLHttpRequest
或获取API发起的跨站点请求。它还希望看到具有跨站点响应的特定HTTP标头。HTTP访问控制(CORS)文章中提供了这些头文件的概述,包括启动请求和处理来自服务器的响应的示例JavaScript代码以及对每个头文件的讨论,应该将其作为配套文章这个。本文介绍处理访问控制请求和制定访问控制响应在PHP中。本文的目标受众是服务器程序员或管理员。尽管这里显示的代码示例是用PHP编写的,但类似的概念适用于ASP.net,Perl,Python,Java等。通常,这些概念可以应用于处理HTTP请求并动态地制定HTTP响应的任何服务器端编程环境。
讨论HTTP标头
这篇文章涵盖了客户端和服务器使用的HTTP标头,并且应该被视为读取前提条件。
工作代码示例
后续章节中的PHP片段(以及对服务器的JavaScript调用)取自此处发布的工作代码示例。这些将在实现跨站点的浏览器中工作XMLHttpRequest
。
简单的跨网站请求
简单访问控制请求在以下情况下启动:
- 一个HTTP/1.1
GET
或者一个POST
被用作请求方法。在POST的情况下,Content-Type
请求体的是一种application/x-www-form-urlencoded
,multipart/form-data
或text/plain.
- 没有自定义标头与HTTP请求一起发送(例如
X-Modified
等)
在这种情况下,可以根据一些考虑将回复发送回去。
- 如果有问题的资源被广泛访问(就像GET访问的任何HTTP资源一样),那么发送回头
Access-Control-Allow-Origin: *
就足够了,除非资源需要诸如Cookie和HTTP认证信息之类的凭证。
- 如果资源应该基于请求者域保持限制,或者如果资源需要使用凭据访问(或设置凭证),则
Origin
可能需要按请求头进行过滤,或者至少回应请求者Origin
(例如Access-Control-Allow-Origin:
http://arunranga.com
)。此外,Access-Control-Allow-Credentials: true
标题将不得不发送。这将在后面的章节中讨论。
简单访问控制请求部分显示客户端和服务器之间的头部交换。这是处理简单请求的PHP代码段:
<?php// We'll be granting access to only the arunranga.com domain // which we think is safe to access this resource as application/xmlif($_SERVER['HTTP_ORIGIN'] == "http://arunranga.com") { header('Access-Control-Allow-Origin: http://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>http://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'获得)是否匹配' http://arunranga.com '。如果是,它会返回Access-Control-Allow-Origin:
http://arunranga.com
。
预先请求的请求
预冲的访问控制请求在下列情况下发生:
- 以外的方法,
GET
或POST
使用,或如果POST
使用具有Content-Type
比其它的一个application/x-www-form-urlencoded
,multipart/form-data
或text/plain
。举例来说,如果Content-Type
所述的POST
体是application/xml
,请求预检。
- 自定义标题(例如
X-PINGARUNER
)与请求一起发送。
Preflighted访问控制请求部分显示客户端和服务器之间的头部交换。响应预检请求的服务器资源需要能够做出以下决定:
- 基于
Origin
(如果有的话)过滤。
- 响应于
OPTIONS
请求(这是预检请求),包括与发送必要的值Access-Control-Allow-Methods
,Access-Control-Allow-Headers
(如果需要,为了任何附加头的应用程序的工作),并且,如果证书是必要的这一资源,Access-Control-Allow-Credentials
。
- 对实际请求的回应,包括处理
POST
数据等。
以下是处理预发光请求的PHP示例:
<?php if($_SERVER['REQUEST_METHOD'] == "GET") { header('Content-Type: text/plain'); echo "This HTTP resource is designed to handle POSTed XML input"; echo "from arunranga.com and not be retrieved with GET"; } elseif($_SERVER['REQUEST_METHOD'] == "OPTIONS") { // Tell the Client we support invocations from arunranga.com and // that this preflight holds good for only 20 days if($_SERVER['HTTP_ORIGIN'] == "http://arunranga.com") { header('Access-Control-Allow-Origin: http://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") { // Handle POST by first getting the XML POST blob, // and then doing something to it, and then sending results to the client if($_SERVER['HTTP_ORIGIN'] == "http://arunranga.com") { $postData = file_get_contents('php://input'); $document = simplexml_load_string($postData); // do something with POST data $ping = $_SERVER['HTTP_X_PINGARUNER']; header('Access-Control-Allow-Origin: http://arunranga.com'); header('Content-Type: text/plain'); echo // some string response after processing } else { die("POSTing Only Allowed from arunranga.com"); }} else { die("No Other Methods Allowed");}?>
请注意,为了响应OPTIONS
预检和POST
数据,将返回相应的标题。因此一个资源处理预检和实际请求。在对OPTIONS
请求的响应中,服务器通知客户端实际的请求确实可以用该POST
方法进行,并且诸如头部字段X-PINGARUNER
可以与实际请求一起发送。这个例子可以在这里看到。
认证请求
凭证访问控制请求 - 即伴随有Cookie或HTTP身份验证信息的请求(并且期望Cookie与响应一起发送) - 可以是简单或预检,具体取决于所使用的请求方法。
在简单请求方案中,请求将以Cookie发送(例如,如果withCredentials
标志设置为开启XMLHttpRequest
)。如果服务器响应Access-Control-Allow-Credentials: true
附加的凭证响应,则响应被客户端接受并暴露给Web内容。在预检请求,服务器可以响应Access-Control-Allow-Credentials: true
的OPTIONS
请求。
以下是一些处理凭证请求的PHP:
<?phpif($_SERVER['REQUEST_METHOD'] == "GET") { header('Access-Control-Allow-Origin: http://arunranga.com'); header('Access-Control-Allow-Credentials: true'); header('Cache-Control: no-cache'); header('Pragma: no-cache'); header('Content-Type: text/plain'); // First See if There Is a Cookie if (!isset($_COOKIE["pageAccess"])) { setcookie("pageAccess", 1, time()+2592000); echo 'I do not know you or anyone like you so I am going to'; echo 'mark you with a Cookie :-)'; } else { $accesses = $_COOKIE['pageAccess']; setcookie('pageAccess', ++$accesses, time()+2592000); echo 'Hello -- I know you or something a lot like you!'; echo 'You have been to ', $_SERVER['SERVER_NAME'], '; echo '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'] == "http://arunranga.com") { header('Access-Control-Allow-Origin: http://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"); } 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:
标头不得有通配符值“*”。它必须提到一个有效的原始域。上面的例子可以看到在这里运行。
Apache示例
限制对某些URI的访问
一个有用的技巧是使用Apache重写,环境变量和标头来应用于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
另请参阅
- Examples of Access Control in Action
- HTTP Access Control covering the HTTP headers
XMLHttpRequest
- Fetch API