Server-Side Access Control

优质
小牛编辑
127浏览
2023-12-01

浏览器发送特定的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-urlencodedmultipart/form-datatext/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

预先请求的请求

预冲的访问控制请求在下列情况下发生:

  • 以外的方法,GETPOST使用,或如果POST使用具有Content-Type 比其它的一个application/x-www-form-urlencodedmultipart/form-datatext/plain。举例来说,如果Content-Type所述的POST体是application/xml,请求预检。
  • 自定义标题(例如X-PINGARUNER)与请求一起发送。

Preflighted访问控制请求部分显示客户端和服务器之间的头部交换。响应预检请求的服务器资源需要能够做出以下决定:

  • 基于Origin(如果有的话)过滤。
  • 响应于OPTIONS请求(这是预检请求),包括与发送必要的值Access-Control-Allow-MethodsAccess-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: trueOPTIONS请求。

以下是一些处理凭证请求的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