目录
一、什么是跨域?
二、为什么会出现跨域问题?
三、常见的跨域场景
四、跨域解决方法
1、JSONP
(1)JSONP原理
(2)JSONP和AJAX对比
(3)JSONP优缺点
(4)JSONP的流程(以第三方API地址为例,不必考虑后台程序)
(5)jQuery的jsonp形式
2、CORS
(1)CORS原理
(2)CORS优缺点
(3)用法
3、WebSocket
4、httpClient内部转发
5、使用nginx搭建企业级接口网关方式
6、设置document.domain解决无法读取非同源网页的 Cookie问题
7、跨文档通信 API:window.postMessage()
本地项目中遇到的跨域
跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。
广义的跨域:
1.) 资源跳转: A链接、重定向、表单提交
2.) 资源嵌入: link script img frame等dom标签,还有样式中background:url()、@font-face()等文件外链
3.) 脚本请求: js发起的ajax请求、dom和js对象的跨域操作等
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
特别说明两点:
第一:如果是协议和端口造成的跨域问题“前台”是无能为力的。
第二:在跨域问题上,域仅仅是通过“URL的首部”来识别而不会根据域名对应的IP地址是否相同来判断。“URL的首部”可以理解为“协议, 域名和端口必须匹配”。
前端部分其实我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景。
出于浏览器的同源策略限制。同源策略/SOP(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)
同源策略限制以下几种行为:
1.) Cookie、LocalStorage 和 IndexDB 无法读取
2.) DOM 和 Js对象无法获得
3.) AJAX 请求不能发送
但是有三个标签是允许跨域加载资源:
1. 2. 3. //向服务器test.com发出请求,该请求的查询字符串有一个callback参数,用来指定回调函数的名字 //处理服务器返回回调函数的数据其中 fn 是客户端注册的回调的函数,目的获取跨域服务器上的json数据后,对数据进行在处理最后服务器返回给客户端数据的格式为:fn({ msg:'this is json data'})(5)jQuery的jsonp形式
JSONP都是GET和异步请求的,不存在其他的请求方式和同步请求,且jQuery默认就会给JSONP的请求清除缓存。$.ajax({ url:"http://crossdomain.com/jsonServerResponse", dataType:"jsonp", //数据类型为jsonp type:"get",//可以省略 jsonpCallback:"fn",//->自定义传递给服务器的函数名,而不是使用jQuery自动生成的,可省略 jsonp:"jsonp",//->把传递函数名的那个形参callback变为jsonp,可省略 success:function (data){ console.log(data); } error : function() { alert('fail'); } });
用法:
①dataType改为jsonp
②jsonp : "jsonp"—发送到后端实际为http://crossdomain.com/jsonServerResponse?jsonp=jQueryxxx ,可以不修改
③后端获取get请求中的jsonp
④构造回调结构
//后端 String jsonpCallback = request.getParameter("jsonp"); //构造回调函数格式jsonp(数据) resp.getWriter().println(jsonp+"("+jsonObject.toJSONString()+")");
2、CORS
(1)CORS原理
CORS 是跨域资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
(2)CORS优缺点CORS要求浏览器(>IE10)和服务器的同时支持,是跨域的根本解决方法,由浏览器自动完成。
优点在于功能更加强大支持各种HTTP Method,缺点是兼容性不如JSONP。
(3)用法
a、前端代码(需要判断浏览器是否支持情况)
function createCORSRequest(method, url) { var xhr = new XMLHttpRequest(); if ("withCredentials" in xhr) { // 此时即支持CORS的情况 // 检查XMLHttpRequest对象是否有“withCredentials”属性 // “withCredentials”仅存在于XMLHTTPRequest2对象里 xhr.open(method, url, true); } else if (typeof!= "undefined") { // 否则检查是否支持XDomainRequest,IE8和IE9支持 // XDomainRequest仅存在于IE中,是IE用于支持CORS请求的方式 xhr = new XDomainRequest(); xhr.open(method, url); } else { // 否则,浏览器不支持CORS xhr = null; } return xhr; } var xhr = createCORSRequest('GET', url); if (!xhr) { throw new Error('CORS not supported'); }
b、服务器端
response.addHeader(‘Access-Control-Allow-Origin:*’);//允许所有来源访问 response.addHeader(‘Access-Control-Allow-Method:POST,GET’);//允许访问的方式
3、WebSocket
Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。//前端代码:
user input://Nodejs socket后台: var http = require('http'); var socket = require('socket.io'); // 启http服务 var server = http.createServer(function(req, res) { res.writeHead(200, { 'Content-type': 'text/html' }); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...'); // 监听socket连接 socket.listen(server).on('connection', function(client) { // 接收信息 client.on('message', function(msg) { client.send('hello:' + msg); console.log('data from client: ---> ' + msg); }); // 断开处理 client.on('disconnect', function() { console.log('Client socket has closed.'); }); });
4、httpClient内部转发
实现原理很简单,若想在B站点中通过Ajax访问A站点获取结果,固然有ajax跨域问题,但在B站点中访问B站点获取结果,不存在跨域问题,这种方式实际上是在B站点中ajax请求访问B站点的HttpClient,再通过HttpClient转发请求获取A站点的数据结果。但这种方式产生了两次请求,效率低,但内部请求,抓包工具无法分析,安全。
$.ajax({ type : "GET", async : false, url : "http://b.b.com:8080/B/FromAjaxservlet?userName=644064", dataType : "json", success : function(data) { alert(data["userName"]); }, error : function() { alert('fail'); } });
@WebServlet("/FromAjaxservlet") public class FromAjaxservlet extends HttpServlet{ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { //创建默认连接 CloseableHttpClient httpClient = HttpClients.createDefault(); //创建HttpGet对象,处理get请求,转发到A站点 HttpGet httpGet = new HttpGet("http://a.a.com:8080/A/FromServlet?userName="+req.getParameter("userName")); //执行 CloseableHttpResponse response = httpClient.execute(httpGet); int code = response.getStatusLine().getStatusCode(); //获取状态 System.out.println("http请求结果为:"+code); if(code == 200){ //获取A站点返回的结果 String result = EntityUtils.toString(response.getEntity()); System.out.println(result); //把结果返回给B站点 resp.getWriter().print(result); } response.close(); httpClient.close(); } catch (Exception e) { } } }
5、使用nginx搭建企业级接口网关方式
www.a.a.com不能直接请求www.b.b.com的内容,可以通过nginx,根据同域名,但项目名不同进行区分。什么意思呢?这么说可能有点抽象。假设我们公司域名叫www.nginxtest.com
当我们需要访问www.a.a.com通过www.nginxtest.com/A访问,并通过nginx转发到www.a.a.com
当我们需要访问www.b.b.com通过www.nginxtest.com/B访问,并通过nginx转发到www.a.a.com
我们访问公司的域名时,是"同源"的,只是项目名不同,此时项目名的作用只是为了区分,方便转发。如果你还不理解的话,先看看是怎么进行配置的:
server { listen 80; server_name www.nginxtest.com; location /A { proxy_pass http://a.a.com:81; index index.html index.htm; } location /B { proxy_pass http://b.b.com:81; index index.html index.htm; } }
我们访问以www.nginxtest.com开头且端口为80的网址,nginx将会进行拦截匹配,若项目名为A,则分发到a.a.com:81。实际上就是通过"同源"的域名,不同的项目名进行区分,通过nginx拦截匹配,转发到对应的网址。整个过程,两次请求,第一次请求nginx服务器,第二次nginx服务器通过拦截匹配分发到对应的网址。
6、设置document.domain解决无法读取非同源网页的 Cookie问题
因为浏览器是通过document.domain属性来检查两个页面是否同源,因此只要通过设置相同的document.domain,两个页面就可以共享Cookie
// 两个页面都设置 document.domain = 'test.com';
7、跨文档通信 API:window.postMessage()
如果两个网页不同源,就无法拿到对方的DOM。典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为*,表示不限制域名,向所有窗口发送。
调用postMessage方法实现父窗口http://test1.com向子窗口http://test2.com发消息(子窗口同样可以通过该方法发送消息给父窗口)
// 父窗口打开一个子窗口 var openWindow = window.open('http://test2.com', 'title'); // 父窗口向子窗口发消息(第一个参数代表发送的内容,第二个参数代表接收消息窗口的url) openWindow.postMessage('Nice to meet you!', 'http://test2.com');
调用message事件,监听对方发送的消息
// 监听 message 消息 window.addEventListener('message', function (e) { console.log(e.source); // e.source 发送消息的窗口 console.log(e.origin); // e.origin 消息发向的网址 console.log(e.data); // e.data 发送的消息 },false);
本地项目中遇到的跨域
想在本地调另一台服务器上的接口,但是一直报跨域的错误,错误如下:
ajax:其中的dftApiContextFile是配置文件中的该接口的路径
最后是以下方式解决的,在这里记录一下:
1、将要调的接口的包和自己的项目在本地启动
2、配置Nginx
(1)将Nginx解压
(2)修改nginx.conf(这里给一个示例,主要是修改为自己的端口号)
nginx通过proxy_pass_http 配置代理站点,upstream实现负载均衡。
#user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; upstream gxweb_pool { ip_hash; server 127.0.0.1:8089; } upstream dataweb_pool { ip_hash; #server 127.0.0.1:8081; server 127.0.0.1:9091; } upstream resource_pool { ip_hash; #server 127.0.0.1:8081; server 127.0.0.1:8085; } server { listen 80; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / { root html; index index.html index.htm; } location ^~ /apply { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://gxweb_pool/apply; proxy_redirect off; } location ^~ /portal { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://dataweb_pool/portal; proxy_redirect off; } location ^~ /dataweb{ proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://dataweb_pool/portal; proxy_redirect off; } location ^~ /resource{ proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://resource_pool/resource; proxy_redirect off; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ \.php$ { # proxy_pass http://127.0.0.1; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} } # another virtual host using mix of IP-, name-, and port-based configuration # #server { # listen 8000; # listen somename:8080; # server_name somename alias another.alias; # location / { # root html; # index index.html index.htm; # } #} # HTTPS server # #server { # listen 443 ssl; # server_name localhost; # ssl_certificate cert.pem; # ssl_certificate_key cert.key; # ssl_session_cache shared:SSL:1m; # ssl_session_timeout 5m; # ssl_ciphers HIGH:!aNULL:!MD5; # ssl_prefer_server_ciphers on; # location / { # root html; # index index.html index.htm; # } #} }
(3)启动Nginx ----运行nginx.exe
3、测试
(1)访问接口包 ---如http://localhost/resource
(2)访问自己的项目 ---如http://localhost/portal
看下代理有没有成功
(3)测试下接口能不能调通,返回数据
(4)看项目中调接口的地方有没有数据显示
参考http://www.imooc.com/article/40074
https://blog.csdn.net/itcats_cn/article/details/82318092
https://blog.csdn.net/weixin_42039396/article/details/80040099