最早由范浩博科学院发表。通常我们在遇到跨域问题的时候,往往会使用cors(跨域资源共享)来解决。不知道大家有没有注意到,在响应头中设置Access-Control-Allow-Origin字段的值时,只允许设置一个域名,也就是说不能同时设置多个域名共享资源。在Yii2中,可以直接使用'Origin'=>['http://www.site1.com','http://www.site2.com']的形式设置多个cors域名值。为什么?实际上,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_headerAccess-Control-Allow-Originhttp://www.fq.yii.com;add_header访问-控制-允许-Originhttp://www.d.yii.com;接口请求和响应头如下:ResponseHeadersAccess-Control-Allow-Origin:http://www.fq.yii.comAccess-Control-Allow-Origin:http://www.d.yii.comConnection:keep-aliveContent-Type:application/json;charset=UTF-8......RequestHeadersAccept:*/*Accept-Encoding:gzip,deflateAccept-Language:zh-CN,zh;q=0.8Host:api.d.fanhaobai.com来源:http://www.fq.yii.comProxy-Connection:keep-alive......当前域名为www.fq.yii.com,需要请求http://api.d.fanhaobai.com的资源/v1/config/list.json跨域。浏览器抛出如下跨域错误:XMLHttpRequestcannotloadhttp://api.d.fanhaobai.com/v1/config/list.json。'Access-Control-Allow-Origin'header包含多个值'http://www.fq.yii.com,http://www.d.yii.com',但只允许一个。因此不允许访问来源'http://www.fq.yii.com'。上面的信息中明确说明Access-Control-Allow-Origin只能设置一个值,即每次请求只能对应一个域名值。所以这种方式不能给cors设置多个域名。Yii2设置多域名Yii2设置多域名cors,只需要在对应的控制器(ConfigController)中设置cors行为即可,如下:classBaseControllerextendsController{/***@inheritdoc*/publicfunctionbehaviors(){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:trueAccess-Control-Allow-Origin:http://www.fq.yii.comConnection:keep-aliveContent-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在controllerbehavior中设置了Origin项,只是一个域名白名单,返回的Access-Control-Allow-Origin与请求的域名一致,在这个白名单中。这个Access-Control-Allow-Origin是Yii2根据当前请求的域名动态处理的。Yii2DynamicAccess-Control-Allow-Origin查看Yii2的\yii\filters\Cors类源码,如下:classCorsextendsActionFilter{/***@vararrayCORSresponseheader*/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-标头'=>[],];/***执行动作前要做的事情*@inheritdoc*/publicfunctionbeforeAction($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);返回真;/***处理cors使用的响应头,动态处理Access-Control-Allow-Origin字段*@paramarray$requestHeadersCORSheaderswehavedetected*@returnarrayCORSheadersreadytobesend*/publicfunctionprepareHeaders($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的需求,但是如果接口异常,会导致跨域设置失败。
