js跨域问题总结

816 阅读4分钟

前后端如何通信

  • Ajax
  • Websoket
  • CORS

为什么会发生ajax跨域

因为浏览器同源策略的限制,不允许ajax跨域请求

同源的限制

不是用一个源的文档,无法操作另一个源的文档

  • 开发时针对浏览器的限制可以用如下命令打开允许跨域的窗口
open /Applications/Google\ Chrome.app --args --disable-web-security --user-data-dir

同源的目的

  • 网站一般会把一些重要信息放在Cookie或者LocalStorage中,如果没有同源策略,这些信息可以共享,如果有人恶意窃取网站数据,这些信息就会被泄露。所以说同源策略是浏览器安全的基础。

跨域带来的问题

  1. cookie、localstorage无法获取
  2. DOM无法操作
  3. Ajax请求无法发送

同源

同源指的是 协议、域名、端口

一个域名地址的组成

  • 一般我们看到的网址没有显示端口号,是因为使用的默认端口80,可以省略
http://(协议)www(子域名).abc.com(主域名):8080(端口号)/somescript/jquery.js(请求资源地址)

可以跨域的三个标签

  1. 图片: <img src='xxx'> :用于打点统计
  2. css: <link href='xxx'>:可以使用CDN资源
  3. js: <script src='xxx'>:可以用于JSONP和CDN资源

跨域的解决办法

JSONP

  • 原理:利用script标签的异步加载来实现

我们给服务端传递一个回调的名称,用加载script标签的形式发送一个请求,服务端给返回一个代码块,代码块中有我传递的回调名称的代码

  • jsonp返回的数据不是json对象而是js脚本,这个脚本是一个函数的调用,函数的名字是jsonp请求的时候写的函数,这个jsonp请求返回的值作为参数。
  • jsonp发送的请求type是script

JSONP是ajax吗

不是

为什么JSONP不是ajax

它是利用script标签的异步加载来实现

使用jsonp服务器后台需要改动吗

需要,所有跨域请求都必须经过信息提供方的允许

缺点

  • 服务器端需要改动,返回值不再是JSON对象,而是JS脚本
  • 发送的不是XHR请求
  • 只能实现get请求,容易遭受XSS攻击

优点

  • 兼容性比较好,服务器改造非常小

原生JS模拟

function jsonp({url,params,cb}) {            
  return new Promise((resolve, reject) => {                
    let script = document.createElement('script');                
    window[cb] = function (params) {                    
      resolve(params);
    }                
    params = {...params,cb};                
    let arrs = [];       
	for(let key in params){                    
		arrs.push(`${key}=${params[key]}`);                    
	}                
    script.src = `${url}?${arrs.join('&')}`;                
    document.body.appendChild(script);            
  });
};

jsonp({            
   url: 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su',            
   params:{wd:'a'},            
   cb:'show'        
}).then(data=>{            
	console.log('jsonp跨域请求的数据为:',data);        
});

jquery模拟

jquery原代码也是通过动态创建script标签的形式实现跨越的

$.ajax({
    url: 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su',
    type: 'get',
    data: {wd:'a'},
    dataType: 'jsonp',  // 请求方式为jsonp
    jsonpCallback: "show",    // 自定义回调函数名
    success:function(){
        
    },
    error:function(){
        
    }
});

vue:axios

this.$http.jsonp('https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su', {
    params: {wd:'a'},
  jsonp: 'show'}).then((res) => {
    console.log(res); 
})

Hash

场景: 当前页面A通过iframe嵌入了跨域页面

  1. A页面中的操作
var B = document.getElementByTagName('iframe')
B.src = B.src + '#' + 'data'
  1. B页面中接收数据
window.onhashchange = function(){
  let data = window.location.hash
}

postMessage

场景:窗口A像跨域的窗口B发送消息

  1. A中发送
Bwindon.postMessage('data','http://B.com')
  1. B中接受
windoq.addEventListener('message',function(){
  console.log(event.origin)
  console.log(event.source)
  console.log(event.data)
},false)

WebSocket

var ws = new WebSocket('wss://echo.websocket.org')
ws.onopen = function(evt){
  console.log('Connection open...')
  ws.send('Hello WebSockets')
}
ws.onmessage = function(evt){
  console.log('Received Message'+ evt.data)
}
ws.onclose = function(evt){
  console.log('Connection closed')
}

CORS 参考资料

服务器端设置http header

response.setHeader('Access-Control-Allow-Origin','http://a.com,http://b.com')
response.setHeader('Access-Control-Allow-Origin','X-Requested-With')
response.setHeader('Access-Control-Allow-Origin','PUT,POST,GET,DELETE,OPTIONS')
// 接收跨域的cookie
response.setHeader('Access-Control-Allow-Credentials','true')

不需要使用预请求去验证的

允许的method,其他的通过设置Access-Control-Allow-Methods:'PUT,DELETE'来设置

  1. GET
  2. HEAD
  3. POST

响应头允许的Content-Type

  1. text/plain、text/html
  2. multipart/form-data
  3. application/x-www-form-urlencoded

允许的请求头,其他都通过设置Access-Control-Allow-Headers来处理

  1. Accept
  2. Accept-Language
  3. Content-Language

设置响应头:任何域名/指定域名可以访问这个服务

'Access-Control-Allow-Origin': '*'
'Access-Control-Allow-Origin': 'http://baidu.com'

CORS预请求

  1. 跨域请求,设置了自定义请求头
    fetch('http://localhost:8887', {
      method: 'POST',
      headers: {
        'X-Test-Cors': '123'
      }
    })
  1. 设置响应头
  response.writeHead(200, {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Headers': 'X-Test-Cors',
    'Access-Control-Allow-Methods': 'PUT,DELELTE',
    'Access-Control-Max-Age':'1000'
  })

3.浏览器会先发送一个options类型的预请求 4.然后在发送真正的请求

  • 通过设置响应头Access-Control-Max-Age,可以设置预请求在多长时间内不需要重复发送

jsonp和cors的对比

1.cors与jsonp的使用目的相同,但是比jsonp更强大。 2.jsonp只支持get请求,cors支持所有类型的http请求。 3.jsonp的优势在于支持老式浏览器,以及可以向不支持cors的网站请求数据。

手动编写一个ajax,不依赖第三方库

  • 不使用promise
var xhr =XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject()
//为保证跨域浏览器兼容,onreadystatechange应该在调用open之前被赋值
xhr.onreadystatechange = function (argument) {
	if(xhr.readyState === 4){
	  if((xhr.status >200 && xhr.status <300)|| xhr.status === 304){
	    alert(xhr.responseTest)
	  }else{
            alert(xhr.status)
          }
}
xhr.open('GET','/api',true)//是否异步请求,true代表异步,false代表同步
xhr.send(null) //请求接口需要的参数,如果没有必须用null代替
  • 使用promise
function ajax(url) {
  const p = new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open('GET', url, true)
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(JSON.parse(xhr.responseText))
        } else {
          reject(new Error('404 Not Found'))
        }
      }
    }
    xhr.send(null)
  })
  return p
}
ajax('/data/test.json')
  .then(res=>console.log(res))
  .catch(res=>console.error(err))