Yii支持多域名cors原理的实现


Posted in PHP onDecember 05, 2018

平常我们遇到跨域问题时,常使用 cors(Cross-origin resource sharin)方式解决。不知你是否注意到,在设置响应头 Access-Control-Allow-Origin 域的值时,只允许设置一个域名,这意味着不能同时设置多个域名来共享资源。而在 Yii2 中直接使用'Origin' => ['http://www.site1.com', 'http://www.site2.com']的形式却可以设置多个 cors 域名值,Why?

Yii支持多域名cors原理的实现

其实,Yii2 中采用了动态设置 Access-Control-Allow-Origin 域值的方法来解决这个问题。

说明:测试使用的接口域名api.d.fanhaobai.com,cros 多域名为www.d.yii.comwww.fq.yii.com

Nginx设置多域名

尝试直接通过 Nginx 的add_header模块追加 Access-Control-Allow-Origin 值实现,如下:

add_header Access-Control-Allow-Origin http://www.fq.yii.com;
add_header Access-Control-Allow-Origin http://www.d.yii.com;

接口 请求 和 响应头 如下:

Response Headers
Access-Control-Allow-Origin: http://www.fq.yii.com
Access-Control-Allow-Origin: http://www.d.yii.com
Connection: keep-alive
Content-Type: application/json; charset=UTF-8
... ...

Request Headers
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Host: api.d.fanhaobai.com
Origin: http://www.fq.yii.com
Proxy-Connection: keep-alive
... ...

当前域为www.fq.yii.com,需跨域请求http://api.d.fanhaobai.com/v1/config/list.json的资源。浏览器抛出如下跨域错误:

XMLHttpRequest cannot load http://api.d.fanhaobai.com/v1/config/list.json. The 'Access-Control-Allow-Origin' header contains multiple values 'http://www.fq.yii.com, http://www.d.yii.com', but only one is allowed. Origin 'http://www.fq.yii.com' is therefore not allowed access.

以上信息明确说明,Access-Control-Allow-Origin 只能设置为一个值,即每次请求只能对应一个域名值。故通过该方法不能设置多域名进行 cors。

Yii2设置多域名

Yii2 设置多域名 cors,只需在对应控制器(ConfigController)中设置 cors 行为,如下:

class BaseController extends Controller
{
  /**
   * @inheritdoc
   */
  public function behaviors()
  {
    return [
      'corsFilter' => [
        'class' => \yii\filters\Cors::className(),
        'cors' => [
          //运行cors域名列表
          'Origin' => ['http://www.d.yii.com', 'http://www.fq.yii.com'],
          'Access-Control-Allow-Credentials' => true,
        ]
      ],
    ];
  }
}

重新在www.fq.yii.com发送 cors 请求,发现此时已经不存在跨域问题。响应头 如下:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://www.fq.yii.com
Connection: keep-alive
Content-Type: application/json; charset=UTF-8
... ...

我们会发现,Access-Control-Allow-Origin 域的值为http://www.fq.yii.com,刚好为当前域名一致,且只有一个值,并未出现设置的http://www.d.yii.com值。

同时,在www.d.yii.com下发送 cors 请求,也不存在跨域问题。响应头中 Access-Control-Allow-Origin 值为http://www.d.yii.com

由此可知,Yii2 在控制器行为中设置 Origin 项,只是一个域名白名单,而返回的 Access-Control-Allow-Origin 同请求的域名一致且在这个白名单中,这个 Access-Control-Allow-Origin 由 Yii2 根据当前请求所在域名进行了动态处理。

Yii2动态Access-Control-Allow-Origin

查看 Yii2 的\yii\filters\Cors类源码,如下:

class Cors extends ActionFilter
{
  /**
   * @var array CORS所用的响应头
   */
  public $cors = [
    'Origin' => ['*'],
    'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
    'Access-Control-Request-Headers' => ['*'],
    'Access-Control-Allow-Credentials' => null,
    'Access-Control-Max-Age' => 86400,
    'Access-Control-Expose-Headers' => [],
  ];
  
  /**
   * 执行action前要做的事
   * @inheritdoc
   */
  public function beforeAction($action)
  {
    $this->request = $this->request ?: Yii::$app->getRequest();
    $this->response = $this->response ?: Yii::$app->getResponse();
    ... ...
    $requestCorsHeaders = $this->extractHeaders();
    //获取cors所用的响应头
    $responseCorsHeaders = $this->prepareHeaders($requestCorsHeaders);
    //设置cors所用的响应头
    $this->addCorsHeaders($this->response, $responseCorsHeaders);
    return true;
  }
  
  /**
   * 处理cors所用的响应头,动态处理Access-Control-Allow-Origin域
   * @param array $requestHeaders CORS headers we have detected
   * @return array CORS headers ready to be sent
   */
  public function prepareHeaders($requestHeaders)
  {
    $responseHeaders = [];
    //$requestHeaders['Origin']为源地址,请求所在域名
    if (isset($requestHeaders['Origin'], $this->cors['Origin'])) {
      //源地址在白名单中,则设置Access-Control-Allow-Origin为源地址
      if (in_array('*', $this->cors['Origin']) || in_array($requestHeaders['Origin'], $this->cors['Origin'])) {
        $responseHeaders['Access-Control-Allow-Origin'] = $requestHeaders['Origin'];
      }
    }
    ... ...
   }
}

主要思想就是,查看源地址是否在 cors 白名单中,在则设置 Access-Control-Allow-Origin 域的值为源地址。这样就能满足 Access-Control-Allow-Origin 为一个值的限制,同时也能允许指定的域名进行 cors。

注意:使用该方法请确保 Nginx 配置中未操作 Access-Control-Allow-Origin 域。

总结

通过 Nginx 设置 Access-Control-Allow-Origin 进行 cors,有且只能有一个特定域名,局限性较大。通过代码逻辑操作 Access-Control-Allow-Origin 来实现 cors,则比较灵活,能解决多个域名进行 cors 的需求,但是如果接口异常,跨域设置则会失效。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
PHP5 安装方法
Oct 09 PHP
PHP中在数据库中保存Checkbox数据(2)
Oct 09 PHP
php 图片上添加透明度渐变的效果
Jun 29 PHP
php 计算两个时间戳相隔的时间的函数(小时)
Dec 18 PHP
PHP 将图片按创建时间进行分类存储的实现代码
Jan 05 PHP
php的webservice的wsdl的XML无法显示问题的解决方法
Mar 11 PHP
php实现的DateDiff和DateAdd时间函数代码分享
Aug 16 PHP
PHP-FPM之Chroot执行环境详解
Aug 03 PHP
php常用数组array函数实例总结【赋值,拆分,合并,计算,添加,删除,查询,判断,排序】
Dec 07 PHP
PHP批量修改文件名称的方法分析
Feb 27 PHP
Vagrant(WSL)+PHPStorm+Xdebu 断点调试环境搭建
Dec 13 PHP
解决Laravel5.x的php artisan migrate数据库迁移创建操作报错SQLSTATE[42000]
Apr 06 PHP
php判断电子邮件是否正确方法
Dec 04 #PHP
浅谈Laravel核心解读之Console内核
Dec 02 #PHP
Laravel使用scout集成elasticsearch做全文搜索的实现方法
Nov 30 #PHP
用Laravel Sms实现laravel短信验证码的发送的实现
Nov 29 #PHP
php实现每日签到功能
Nov 29 #PHP
PHP序列化的四种实现方法与横向对比
Nov 29 #PHP
PHP中如何使用Redis接管文件存储Session详解
Nov 28 #PHP
You might like
PHP采集类Snoopy抓取图片实例
2014/06/19 PHP
php输出全球各个时区列表的方法
2015/03/31 PHP
javascript入门·对象属性方法大总结
2007/10/01 Javascript
jquery 如何动态添加、删除class样式方法介绍
2012/11/07 Javascript
JavaScript判断DOM何时加载完毕的技巧
2012/11/11 Javascript
Jquery动态进行图片缩略的原理及实现
2013/08/13 Javascript
JS获取URL中的参数数据
2013/12/05 Javascript
TypeScript Type Innference(类型判断)
2016/03/10 Javascript
JavaScript实现弹出DIV层同时页面背景渐变成半透明效果
2016/03/25 Javascript
使用do...while的方法输入一个月中所有的周日(实例代码)
2016/07/22 Javascript
ztree实现权限横向显示功能
2017/05/20 Javascript
详解vue-cli构建项目反向代理配置
2017/09/07 Javascript
详解如何用webpack4从零开始构建react开发环境
2019/01/27 Javascript
详解mpvue中小程序自定义导航组件开发指南
2019/02/11 Javascript
如何正确理解vue中的key详解
2019/11/02 Javascript
vue proxy 的优势与使用场景实现
2020/06/15 Javascript
vue 实现setInterval 创建和销毁实例
2020/07/21 Javascript
VUE中setTimeout和setInterval自动销毁案例
2020/09/07 Javascript
[02:41]DOTA2亚洲邀请赛小组赛第三日 赛事回顾
2015/02/01 DOTA
将python代码和注释分离的方法
2018/04/21 Python
python框架中flask知识点总结
2018/08/17 Python
浅谈pycharm下找不到sqlalchemy的问题
2018/12/03 Python
django框架基于queryset和双下划线的跨表查询操作详解
2019/12/11 Python
python3 实现口罩抽签的功能
2020/03/11 Python
利用Vscode进行Python开发环境配置的步骤
2020/06/22 Python
New Balance加拿大官方网站:运动鞋和健身服装
2018/11/19 全球购物
法国床上用品商店:La Compagnie du lit
2019/12/26 全球购物
什么是用户模式(User Mode)与内核模式(Kernel Mode) ?
2015/09/07 面试题
公益广告语集锦
2014/03/13 职场文书
捐赠仪式主持词
2014/03/19 职场文书
协议书与合同的区别
2014/04/18 职场文书
2014年安全工作总结范文
2014/11/13 职场文书
教师党员承诺书2015
2015/01/21 职场文书
标准版个人借条怎么写?以及什么是借条?
2019/08/28 职场文书
分布式架构Redis中有哪些数据结构及底层实现原理
2022/03/13 Redis
Android Canvas绘制文字横纵向对齐
2022/06/05 Java/Android