Server-Side Access Control - HTTP 编辑
浏览器会针对从 XMLHttpRequest
或Fetch API中发起的跨网站请求发送特定的HTTP标头。它还希望看到使用跨站点响应发送回的特定HTTP标头。这些标头的概述,包括启动请求和处理来自服务器的响应的示例JavaScript代码, 以及每个头的讨论,可以在HTTP访问控制(CORS)文章中找到,应该作为本文的配套文章阅读。
HTTP访问控制文章是很好的使用指南。本文介绍利用PHP处理访问控制请求和制定访问控制响应。本文的目标读者是服务器程序员或管理员。虽然在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: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-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'] == "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
参见
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论