引言
随着前后端分离技术的普及,跨域问题成为开发中常见的挑战。本文将详细介绍前后端分离的优势、跨域问题的原理以及常见的解决方案。
前后端分离的优势
减轻服务器压力,提升前端性能。
前端和后端解耦,便于开发和维护。
提升用户体验,减少页面刷新。
跨域问题的原理
跨域问题的根本原因是什么? 因为浏览器受到同源策略的限制,当前域名只能请求同域下xhr服务的属性。
什么叫做同源策略? 就是不同的域名, 不同端口, 不同的协议不允许共享资源的,保障浏览器安全。同源策略是针对浏览器设置的门槛。如果绕过浏览就能实现跨域访问。
跨域问题的解决方案 修改浏览器配置
既然跨域问题的根本原因是浏览器的限制,那么就可以配置浏览器来规避这个问题
1 "C:\ProgramFiles(x86)\Google\Chrome\Application\chrome.exe" --disable-web-security--user-data-dir
以Google Chrome为例,浏览器以上面这种命令打开,就解除了同源限制。
使用jsonp 这是一个JQuery中正常的AJAX请求的代码片段
1 2 3 4 5 6 7 8 9 10 11 $("#demo1" ).click (function ( ){ $.ajax ({ url : 'http://www.tpadmin.top/Index/Test/crossDomain' , data : {}, type : 'get' , success : function (res ) { alert (res); } }); });
那么,如果在JQuery中的使用JSONP的AJAX请求呢,看下面的示例
1 2 3 4 5 6 7 8 9 10 11 $("#demo2" ).click (function ( ){ $.ajax ({ url : 'http://www.tpadmin.top/Index/Test/crossDomain' , data : {}, type : 'get' , dataType : 'jsonp' , success : function (res ) { alert (res); } }); });
这时候看到 请求的网址自动变成了
1 http://www.tpadmin.top/Index/Test/crossDomain?callback=jQuery331015214102388989237_1534993962395&_=1534993962396
由于跨域访问的只限制xhr类型的请求(上文中已经说了),所以js中就利用了这一特点,让服务端不在返回的是一个JSON格式的数据,而是返回一段JS代码,将JSON的数据以参数的形式传递到这个函数中,而函数的名称就是callback参数的值,所以还需要修改服务端的代码,代码如下
1 2 3 4 5 6 7 8 <?php $callback = isset ($_GET ['callback' ])?$_GET ['callback' ]:'' ; if (!empty ($callback )) { $arr = ['code' => 200 , 'name' => 'cui' ]; $data = json_encode ($arr ); exit ($callback . '(' . $data . ')' ); } ?>
OK,现在问题解决了,但是JSONP存在着诸多限制,比如:
JSONP只支持GET请求,什么?你要提交表单,sorry,此路不通
它只支持跨域HTTP请求
这些问题让很多人不得不放弃它,所以出现了下面的解决办法。
CORS规范 CORS基础使用 回归问题本质,跨域问题为什么会产生,上面已经说了,是由于浏览器的限制,那么在执行过程中有什么不同,下面两张度分析一下(主要看请求头的部分):
这是非跨域请求 这是跨域请求
这时发现跨域访问的请求头中存在Origin的字段,用来记录当前的访问域名,可以在服务端增加一个响应头Access-Control-Allow-Origin来告诉浏览器服务端允许它获取就可以了。
1 2 3 4 5 <?php header ('Access-Control-Allow-Origin:http://127.0.0.1' );$arr = ['code' => 200 , 'name' => 'cui' ];echo $data = json_encode ($arr );?>
配置允许多个域名访问:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php $requestHeader = getallheaders ();$origin = isset ($requestHeader ['Origin' ])?$requestHeader ['Origin' ]:'' ;switch ($origin ) { case 'http://127.0.0.1' : header ('Access-Control-Allow-Origin:http://127.0.0.1' ); break ; case 'http://localhost' : header ('Access-Control-Allow-Origin:http://localhost' ); break ; default : break ; } $arr = ['code' => 200 , 'name' => 'cui' ];echo $data = json_encode ($arr );?>
配置允许所有域名访问:
1 2 3 4 5 <?php header ('Access-Control-Allow-Origin:*' );$arr = ['code' => 200 , 'name' => 'cui' ];echo $data = json_encode ($arr );?>
到这里,其实已经结束了,但还有一些其他的特殊情况
请求方法不是GET、HEAD、POST
请求头中存在自定义头
Content-Type不是text/plain、multipart/form-data、application/x-www-form-urlencoded
服务端需要获取到客户端的Cookie
CORS应对“非简单请求”(预检请求) 为了测试各种限制,再来看一段代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $("#demo1" ).click (function ( ){ $.ajax ({ url : 'http://cui.tpadmin.top/crossDomain.php' , data : {}, type : 'PUT' , contentType : 'application/json' , header : { token :'asdfgqwerttyyazxcvbvb' }, success : function (res ) { alert (res); } }); });
虽然在服务端加入了Access-Control-Allow-Origin响应头,但是如果出现上面所说的情况时,需要做一些特殊的设置,修改服务端代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php header ('Access-Control-Allow-Headers:Content-Type' );header ('Access-Control-Allow-Methods:PUT' );$requestHeader = getallheaders ();$origin = isset ($requestHeader ['Origin' ])?$requestHeader ['Origin' ]:'' ;switch ($origin ) { case 'http://127.0.0.1' : header ('Access-Control-Allow-Origin:http://127.0.0.1' ); break ; case 'http://localhost' : header ('Access-Control-Allow-Origin:http://localhost' ); break ; default : break ; } $arr = ['code' => 200 , 'name' => 'cui' ];echo $data = json_encode ($arr );?>
这里需要区分一下简单请求模式与非简单请求模式:
请求方法只能为GET、HEAD、POST
请求头中无自定义头
Content-Type必须为text/plain、multipart/form-data、application/x-www-form-urlencoded
符合以上条件的为简单请求,否则为非简单请求。非简单请求中,浏览器会默认发送两条请求,第一条为预检请求(OPTION),第二条为AJAX的请求,出于服务器的性能考量,一般需要将预检命令进行缓存,而不是每次都执行预检请求,可以修改代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php header ('Access-Control-Allow-Headers:Content-Type' );header ('Access-Control-Allow-Methods:PUT' );header ('Access-Control-Max-Age:3600' );$requestHeader = getallheaders ();$origin = isset ($requestHeader ['Origin' ])?$requestHeader ['Origin' ]:'' ;switch ($origin ) { case 'http://127.0.0.1' : header ('Access-Control-Allow-Origin:http://127.0.0.1' ); break ; case 'http://localhost' : header ('Access-Control-Allow-Origin:http://localhost' ); break ; default : break ; } $arr = ['code' => 200 , 'name' => 'cui' ];echo $data = json_encode ($arr );?>
这次请求了两次,发现第二次请求没有发送预检请求,新增加的代码代表允许缓存的时间(3600S)。
CORS应对Cookie、自定义头等情况 除了以上的各种配置外,还有很多其他的比如,比如
1 2 3 4 5 <?php header ('Access-Control-Allow-Credentials:true' );header ('Access-Control-Allow-Headers:token,Content-Type,...' );
使用nginx或apache的配置 上面的例子实现了跨域访问,但是如果不想在代码中修改,还有其他的方法,比如可以直接修改服务软件(nginx、apache)
Apache的配置(想使用Header的话,需要加载mod_headers.so这个模块)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <VirtualHost *:80> ServerName localhost ServerAlias localhost DocumentRoot "${INSTALL_DIR}/www" Header always set Access-Control-Allow-Header "PUT" Header always set Access-Control-Allow-Credentials "true" Header always set Access-Control-Max-Age "3600" #这里设置的为全匹配 Header always set Access-Control-Allow-Origin "expr=%{req:origin}" #这里设置的为全匹配 Header always set Access-Control-Allow-Headers "expr=%{req:Access-Control-Allow-Headers}" <Directory "${INSTALL_DIR}/www/"> Options +Indexes +Includes +FollowSymLinks +MultiViews AllowOverride All Require local </Directory> </VirtualHost>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 server { listen 80 default_server; listen [::]:80 default_server; server_name _; root /usr/share/nginx/html; # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; location / { #支持其他请求 add_header Access-Control-Allow-Methods PUT; #设置预检请求的缓存 add_header Access-Control-Max-Age 3600; #允许Cookie add_header Access-Control-Allow-Credentials true; #这里最好做判断,怕麻烦的话就写*,但是不建议 if ($http_origin = http://localhost){ add_header Access-Control-Allow-Origin http://localhost; } if ($http_origin = http://127.0.0.1){ add_header Access-Control-Allow-Origin http://127.0.0.1; } #为了方便,这样写了 add_header Access-Control-Allow-Headers $http_access_control_request_headers; if ($request_method = OPTIONS){ return 200; } } error_page 404 /404.html; location = /40x.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } }
其他方法
比如使用nginx代理,比如客户端A域名访问服务端B域名,可以在A域名下配置代理服务重定向到B域名,逻辑上避开了浏览器的跨域操作,所以是可以实现跨域访问的。
总结 本文介绍了前后端分离的优势以及跨域问题的原理和解决方案。在实际开发中,推荐使用CORS解决跨域问题,并注意安全性配置。
放学作业 Q:前端服务A域名、API服务端B域名、GO程序自启动监听C域名/IP,它们的逻辑架构为:
其中的B域名使用nginx代理将所有请求重定向到C域名下
A域名通过JS请求B域名的API数据,渲染页面数据
那么,在这种情况下,应该将CORS配置在那个服务下?