跨域

110 阅读3分钟

- 什么是跨域

跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。

当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域

- 广义的跨域:

1.) 资源跳转: A链接、重定向、表单提交
2.) 资源嵌入: <link><script><img><frame>等dom标签,
    还有样式中background:url()、
    @font-face()等文件外链
3.) 脚本请求: js发起的ajax请求、dom和js对象的跨域操作等
当前页面url被请求页面url是否跨域原因
www.test.com/www.test.com/index.html同源(协议、域名、端口号相同)
www.test.com/www.test.com/index.html跨域协议不同(http/https)
www.test.com/www.baidu.com/跨域主域名不同(test/baidu)
www.test.com/blog.test.com/跨域子域名不同(www/blog)
www.test.com:8080/www.test.com:7001/跨域端口号不同(8080/7001)

- 什么是同源策略

所谓同源(即指在同一个域)就是两个页面具有相同的协议,域名和端口号,即便两个不同的域名指向同一个ip地址,也非同源。

解决方案:

1、cors:服务器设置访问的白名单

2、jsonp:利用html的有些标签或者属性不受同源策略的限制,只能应用于get请求

3、代理服务器:服务器之间传递数据不受同源策略限制

同源策略限制以下几种行为:

1.) CookieLocalStorageIndexDB 无法读取
2.) DOMJs对象无法获得
3.) AJAX 请求不能发送

- 跨域解决方案

1、 通过jsonp跨域
2document.domain + iframe跨域 
3、 location.hash + iframe 
4window.name + iframe跨域
5、 postMessage跨域 
6、 跨域资源共享(CORS7、 nginx代理跨域 
8、 nodejs[中间件](https://cloud.tencent.com/product/tdmq?from=10680)代理跨域
9WebSocket协议跨域

1. 通过jsonp跨域

jsonp缺点:只能实现get一种请求。

(1)原生实现:

<script>
 var script = document.createElement('script');
    script.type = 'text/javascript';   
 // 传参并指定回调执行函数为onBack
 script.src = 'http://www.domain2.com:8080/
              login?user=admin&callback=onBack';    
 document.head.appendChild(script);    
 // 回调执行函数
    function onBack(res) {
        alert(JSON.stringify(res));
    } </script>

服务端返回如下(返回时即执行全局函数):

onBack({"status": true, "user": "admin"})

(2)jquery ajax:

$.ajax({    
url: 'http://www.domain2.com:8080/login',    
type: 'get',    dataType: 'jsonp',  
// 请求方式为jsonp
    jsonpCallback: "onBack",    
// 自定义回调函数名
    data: {}
});

(3)vue.js:

this.$http.jsonp('
http://www.domain2.com:8080/login', 
{    
params: {},    
jsonp: 'onBack'}).then((res) => 
{    console.log(res); 
})

后端node.js代码示例:

var querystring = require('querystring');
var http = require('http');
var server = http.createServer();
server.on('request', function(req, res) {    
var params = qs.parse(req.url.split('?')[1]);    
var fn = params.callback;    
// jsonp返回设置
    res.writeHead(200, { 
'Content-Type': 'text/javascript' });
    res.write(fn + '(' + 
       JSON.stringify(params) + ')');
    res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');

2. 跨域资源共享(CORS)

CORS属于跨源 AJAX 请求的根本解决方法。

1、普通跨域请求:只需服务器端设置Access-Control-Allow-Origin

2、带cookie跨域请求:前后端都需要进行设置

【前端设置】根据xhr.withCredentials字段判断是否带有cookie

1、 前端设置:

(1)原生ajax

// 前端设置是否带
cookiexhr.withCredentials = true;

示例代码:

var xhr = new XMLHttpRequest();
// IE8/9需用
window.XDomainRequest兼容
// 前端设置是否带
cookiexhr.withCredentials = true;
xhr.open('post', 
'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader
('Content-Type', '
application/x-www-form-urlencoded');
xhr.send('user=admin');
xhr.onreadystatechange = function() 
{    
if (xhr.readyState == 4 && xhr.status == 200) 
{
   alert(xhr.responseText);
    }
};

(2)jQuery ajax

$.ajax({
    ...
 xhrFields: {
 withCredentials: true    
 // 前端设置是否带cookie   
 },
 crossDomain: true,   
 // 会让请求头中包含跨域的额外信息,
 但不会含cookie
    ...
});

(3)vue框架 在vue-resource封装的ajax组件中加入以下代码:

Vue.http.options.credentials = true

2.服务端设置:

若后端设置成功,前端浏览器控制台则不会出现跨域报错信息,反之,说明没设成功。

(1.)Java后台:

/*
 * 导入包:
  import javax.servlet.http.HttpServletResponse;
 * 接口参数中定义:
  HttpServletResponse response
 */response.setHeader
  ("Access-Control-Allow-Origin",
 "http://www.domain1.com");  
// 若有端口需写全(协议+域名+端口)
response.setHeader
("Access-Control-Allow-Credentials", "true");

(2.)Nodejs后台示例:

var http = require('http');
var server = http.createServer();
var qs = require('querystring');
server.on('request', function(req, res) 
{    
var postData = '';    
// 数据块接收中
 req.addListener('data', 
  function(chunk) {
   postData += chunk;
    });    
// 数据接收完毕
 req.addListener('end', 
function() 
{
  postData = qs.parse(postData);        
// 跨域后台设置
   res.writeHead(200, {           
'Access-Control-Allow-Credentials': 
  'true',    
 // 后端允许发送Cookie
 'Access-Control-Allow-Origin': 
 'http://www.domain1.com',   
 // 允许访问的域(协议+域名+端口)
   'Set-Cookie': 
 'l=a123456;Path=/;Domain=www.domain2.com;
  HttpOnly'  
  // HttpOnly:脚本无法读取cookie
        });
  res.write(JSON.stringify(postData));
   res.end();
    });
});
server.listen('8080');
console.log('Server is running at port 8080...');

3. Nodejs中间件代理跨域

node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。

1.非vue框架的跨域(2次跨域)

利用node + express + http-proxy-middleware搭建一个proxy服务器。

(1)前端代码示例:

var xhr = new XMLHttpRequest();
// 前端开关:
浏览器是否读写cookiexhr.withCredentials = true;
// 访问http-proxy-middleware代理服务器
xhr.open('get', '
http://www.domain1.com:3000/login?
  user=admin', true);
xhr.send();

(2)中间件服务器:

var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();
app.use('/', proxy({    
// 代理跨域目标接口
target: 'http://www.domain2.com:8080',    
changeOrigin: true,    
// 修改响应头信息,实现跨域并允许带cookie
    onProxyRes: function(proxyRes, req, res) {
    res.header('Access-Control-Allow-Origin', 
    'http://www.domain1.com');
   res.header('Access-Control-Allow-Credentials', 
    'true');
    },   
     // 修改响应信息中的cookie域名
    cookieDomainRewrite: 'www.domain1.com'  
     // 可以为false,表示不修改}));
app.listen(3000);console.log('
     Proxy server is listen at port 3000...');

(3.)Nodejs后台同(六:nginx)

2、 vue框架的跨域(1次跨域)

利用node + webpack + webpack-dev-server代理接口跨域。在开发环境下,由于vue渲染服务和接口代理服务都是webpack-dev-server同一个,所以页面与代理接口之间不再跨域,无须设置headers跨域信息了。

webpack.config.js部分配置:

module.exports = {    
entry: {},    
module: {},
    ...
devServer: {        
historyApiFallback: true,        
proxy: [{            
context: '/login',            
target: 'http://www.domain2.com:8080',  
// 代理跨域目标接口
    changeOrigin: true,            
secure: false,  
// 当代理某些https服务报错时用
   cookieDomainRewrite: 'www.domain1.com'  
// 可以为false,表示不修改
        }],        
noInfo: true
    }
}