PHP 解决 CORS 跨域问题

Posted by Weafung on 2016-11-21

背景

最近和hbb在写一个项目,采用前后端分离的模式。我写接口,hbb写前端。接口采用json的格式进行数据交互,前端和后端放在了不同的服务器上,一开始调试的时候就遇上了问题。

问题

前端使用JS在POST数据到接口时,出现问题。

Google Chrome的提示:

XMLHttpRequest cannot load http://weafung.com/index.php/. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access.

Mozilla Firefox给出了更直观的提示:

已拦截跨源请求:同源策略禁止读取位于 http://weafung.com/index.php/ 的远程资源。(原因:CORS 头缺少 'Access-Control-Allow-Origin')。

解决

知道哪里出现问题后就要解决,首先了解下CORS。

什么是CORS?

来自维基百科的解释: 跨来源资源共享 - 维基百科,自由的百科全书

跨来源资源共享(CORS)是一份浏览器技术的规范,提供了 Web 服务从不同网域传来沙盒脚本的方法,以避开浏览器的同源策略,是 JSONP 模式的现代版。与 JSONP 不同,CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求。用 CORS 可以让网页设计师用一般的 XMLHttpRequest,这种方式的错误处理比 JSONP 要来的好。另一方面,JSONP 可以在不支持 CORS 的老旧浏览器上运作。现代的浏览器都支持 CORS。

解决过程

在大概了解了CORS之后,初步搜索到大部分的解决方案是在PHP中添加header头:

1
header("Access-Control-Allow-Origin: *");

添加、测试,现在报错变了:

Google Chrome:

XMLHttpRequest cannot load http://weafung.com/index.php/. Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.

Mozilla Firefox:

已拦截跨源请求:同源策略禁止读取位于 http://weafung.com/index.php/ 的远程资源。(原因:来自 CORS 预检通道的 CORS 头 'Access-Control-Allow-Headers' 的令牌 'content-type' 无效)。

那我再加条header试试?

1
2
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: *");

继续失败,还是一样的错误。

在参考 Mozilla Documentation 后发现:Access-Control-Allow-Origin是支持*,而在Access-Control-Allow-Headers部分却没有提到支持*通配符。

再来:

1
2
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept");

咦,好像成功了!!!

解决方法

  • 方法一:在PHP中添加两条header语句

    1
    2
    header("Access-Control-Allow-Origin: *");
    header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept");
  • 方法二:使用来自stackoverflow的cors方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function cors() {
    // Allow from any origin
    if (isset($_SERVER['HTTP_ORIGIN'])) {
    // Decide if the origin in $_SERVER['HTTP_ORIGIN'] is one
    // you want to allow, and if so:
    header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
    header('Access-Control-Allow-Credentials: true');
    header('Access-Control-Max-Age: 86400'); // cache for 1 day
    }
    // Access-Control headers are received during OPTIONS requests
    if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
    if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
    header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
    if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
    header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
    }
    }
  • 方法三:不需要修改PHP的解决办法,使用代理或反向代理
    具体请参考:CORS 跨域问题 - Chs97’s Blog