Yii支持多域名cors原理的实现
平常我们遇到跨域问题时,常使用 cors(cross-origin resource sharin)方式解决。不知你是否注意到,在设置响应头 access-control-allow-origin 域的值时,只允许设置一个域名,这意味着不能同时设置多个域名来共享资源。而在 yii2 中直接使用'origin' => ['http://www.site1.com', 'http://www.site2.com']
的形式却可以设置多个 cors 域名值,why?
其实,yii2 中采用了动态设置 access-control-allow-origin 域值的方法来解决这个问题。
说明:测试使用的接口域名api.d.fanhaobai.com
,cros 多域名为www.d.yii.com
和www.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 的需求,但是如果接口异常,跨域设置则会失效。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。