什么是跨域?跨域问题怎么解决?

目录

一、什么是跨域?

二、为什么会出现跨域问题?

三、常见的跨域场景

四、跨域解决方法

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.<img src=XXX> 
2.<link href=XXX> 
3.<script src=XXX>

三、常见的跨域场景

当前页面URL被请求页面URL是否跨域原因
http://www.test.com/http://www.test.com/index.html同源(协议、域名、端口号相同)
http://www.test.com/https://www.test.com/index.html跨域协议不同(http/https)
http://www.test.com/http://www.baidu.com/跨域主域名不同(test/baidu)
http://www.test.com/http://blog.test.com/跨域子域名不同(www/blog)
http://www.test.com:8080/http://www.test.com:7001/跨域端口号不同(8080/7001)
http://www.domain.com/a.jshttp://192.168.4.12/b.js跨域域名和域名对应相同IP,也非同源

四、跨域解决方法

但所有的跨域都必须经过信息提供方的允许。如果未经允许即可获取,那是浏览器同源策略出现漏洞。

1、JSONP

(1)JSONP原理

利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 数据。服务器收到请求后,将数据放在一个指定名字的回调函数的参数位置传回来。JSONP请求一定需要对方的服务器做支持才可以。

(2)JSONP和AJAX对比

JSONP和AJAX相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求), Jquery中ajax的核心是通过 XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加<script>标签来调用服务器提供的 js脚本。当我们正常地请求一个JSON数据的时候,服务端返回的是一串JSON类型的数据,而我们使用 JSONP模式来请求数据的时候服务端返回的是一段可执行的JavaScript代码。

(3)JSONP优缺点

JSONP优点是兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get方法具有局限性。

(4)JSONP的流程(以第三方API地址为例,不必考虑后台程序)

  • 声明一个回调函数,其函数名(如fn)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)。

  • 创建一个<script>标签,把那个跨域的API数据接口地址,赋值给script的src,还要在这个地址中向服务器传递该函数名(可以通过问号传参:?callback=fn)。

  • 服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是fn,它准备好的数据是fn([{"name":"jianshu"}])。

  • 最后服务器把准备的数据通过HTTP协议返回给客户端,客户端再调用执行之前声明的回调函数(fn),对返回的数据进行操作。

<script type="text/javascript" src="http://test.com/jsonServerResponse?callback=fn"></script>
//向服务器test.com发出请求,该请求的查询字符串有一个callback参数,用来指定回调函数的名字


//处理服务器返回回调函数的数据
<script type="text/javascript">     
    function fn(data) {        
         alert(data.msg);     
    } 
</script> 
其中 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的浏览器提供了向下兼容。
//前端代码: 
<div>user input:<input type="text"></div> 

<script src="./socket.io.js"></script> 

<script> 
    var socket = io('http://www.domain2.com:8080'); 

    // 连接成功处理 
    socket.on('connect', function() {     

      // 监听服务端消息   
      socket.on('message', function(msg) {         
            console.log('data from server: ---> ' + msg);      
      });    
   
      // 监听服务端关闭
      socket.on('disconnect', function() {          
            console.log('Server socket has closed.');      
      }); 
    }); 

    document.getElementsByTagName('input')[0].onblur = function() { 
        socket.send(this.value); 
    }; 
</script>

//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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值