欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-11-12 18:57:58
平常我们遇到跨域问题时,常使用 cors(cross-origin resource sharin)方式解决。不知你是否注意到,在设置响应头 access-control-...

平常我们遇到跨域问题时,常使用 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 的需求,但是如果接口异常,跨域设置则会失效。

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