MDN wants to learn about developers like you: https://qsurvey.mozilla.com/s3/MDN-dev-survey

Server-Side Access Control

浏览器会针对从 XMLHttpRequestFetch API中发起的跨网站请求发送特定的HTTP标头。它还希望看到使用跨站点响应发送回的特定HTTP标头。这些标头的概述,包括启动请求和处理来自服务器的响应的示例JavaScript代码, 以及每个头的讨论,可以在HTTP访问控制(CORS)文章中找到,应该作为本文的配套文章阅读。

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-urlencodedmultipart/form-data, 或text/plain
  • 在请求中,不会发送自定义的头部(如X-Modified)

以下情况,请求会返回相关响应信息

  • 如果资源是允许公开访问的(就像任何允许GET访问的 HTTP资源),返回Access-Control-Allow-Origin:*头信息就足够了,除非是一些需要Cookies和HTTP身份验证信息的请求。
  • 如果资源访问被限制基于相同的域名,或者如果要访问的资源需要凭证(或设置凭证),那么就有必要对请求头信息中的ORIGIN进行过滤,或者至少响应请求的来源(例如Access-Control-Allow-Origin:http://arunranga.com)。另外,将发送Access-Control-Allow-Credentials:TRUE头信息,这在后续部分将进行讨论。

 简单的访问控制请求 介绍了在客户端和服务端进行信息交换的HEADER.  下面是一段用来处理简单请求的PHP代码。

<?php

// 我们将只授予 arunranga.com 域的访问权限,因为我们认为它通过 application/xml 方式来访问这些资源是安全的。

if($_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发送application/x-www-form-urlencodedmultipart/form-data, or text/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'] == "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")
{
    /* 通过首先获得XML传送过来的blob来处理POST请求,然后做一些处理, 最后将结果返回客户端
    */
    if($_SERVER['HTTP_ORIGIN'] == "http://arunranga.com")
    {
            $postData = file_get_contents('php://input');
            $document = simplexml_load_string($postData);
            
            // 对POST过来的数据进行一些处理

            $ping = $_SERVER['HTTP_X_PINGARUNER'];
           
                       
            header('Access-Control-Allow-Origin: http://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: http://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: http://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'] == "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");
    //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

参见

文档标签和贡献者

标签: 
 此页面的贡献者: xgqfrms-GitHub, holynewbie, jearylee
 最后编辑者: xgqfrms-GitHub,