什么是同源策略,前端解决跨域问题的方法
同源策略
同源策略/SOP(Same origin policy)是一种约定,是浏览器最核心也最基本的安全功能,现在所有支持 JavaScript 的浏览器都会使用这个策略。如果缺少了同源策略,浏览器很容易受到 XSS、 CSFR 等攻击。
1.非同源的限制
当一个页面中使用XMLHTTPRequest(XHR请求)对象发送HTTP请求时,必须保证当前页面和请求的对象是同源的,即协议、域名和端口号要完全一致,否则浏览器就会阻止此跨域请求返回的数据。
2.什么是跨域?
由于浏览器为了防止CSRF攻击(Cross-site request forgery跨站请求伪造),避免恶意攻击而带来的风险而采取的同源策略限制。
如何解决跨域
1.后端解决
CORS (Cross-Origin Resource Sharing),跨域资源共享。CORS是官方的跨域解决方案,它的特点是不需要在客户端做任何特殊的操作,完全在服务器中进行处理,支持 get 和 post 请求。跨域资源共享标准新增了一组HTTP首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源|
CORS是通过设置一个响应头来告诉浏览器,该请求允许跨域,浏览器收到该响应以后就会对响应放行。
response.setHeade('Access-Control-Allow-Origin','*') //设置所有的请求地址都允许跨域 response.setHeade('Access-Control-Allow-Origin','http//127.0.0.1:5500') //只有127.0.0.1:5500允许跨域 response.setHeade('Access-Control-Allow-Origin-Method','*') //设置所有的请求方法都允许跨域
2.前端解决
- jsonp(常用)
- 代理服务器(常用)
- websocket(套接字,走的是tcp-ip协议)
- 通过 iframe script link 标签请求资源(src、href)
jsonp 实现跨域请求
浏览器对script标签src属性、link标签ref属性和img标签src属性等没有这这种限制,利用这个“漏洞”就可以很好的解决跨域请求。JSONP就是利用了script标签无同源限制的特点来实现的,当向第三方站点请求时,我们可以将此请求放在<script>标签的src属性里,这就如同我们请求一个普通的JS脚本,可以自由的向不同的站点请求
通过在请求的 url 后指定一个回调函数,然后服务器在返回数据的时候,会构建一个 json 数据的包装,这个包装就是回调函数,然后返回给前端,前端接收到数据后,因为请求的是脚本文件,所以会直接执行,这样我们先前定义好的回调函数就可以被调用,从而实现了跨域请求的处理。这种方式只能用于 get 请求。
JSONP的优点:
JSONP的缺点:
它只支持GET请求而不支持POST等其它类型的HTTP请求; 所以jsonp使用在查询场景居多
实现示例
wd表示关键词,cb表示回调函数。通过回调函数可以将响应的结果拿到
<script> function fn(res){ console.log(res); } </script> <script src="https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=奥特曼&cb=fn"></script>
function jsonp(url,param={},paramName,callback){ if(typeof url != 'string'){ throw new Error('url必须为字符串') } url += '?' // 将param转为&拼接 for(let key in param){ url += `&${key}=${param[key]}` } //函数名需要加工(保持的函数名的唯一) let callbackName = 'fn' + Date.now() + Math.ceil(Math.random()*10) //加给对应的window window[callbackName] = callback //将参数名和回调函数名传入 url += `&${paramName}=${callbackName}` //创建一个script标签 let script = document.createElement('script') //指定对应的src地址 script.src = url //加给body document.body.appendChild(script) //script标签加载完毕 script.onload = function(){ this.remove() //将script标签删除 delete window[callbackName] //将对应的属性删除 } }
测试代码:
<body> <input type="text" class="input" /> <ul class="showBox"></ul> <script src="./jsonp.js"></script> <script> let inp = document.querySelector('.input'); let showBox = document.querySelector('.showBox'); inp.oninput = function () { let wd = this.value; if (wd) { jsonp( 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su', { wd, }, 'cb', function (res) { showBox.innerHTML = ''; res.s.forEach((v) => { showBox.innerHTML += `<li>${v}</li>`; }); } ); } else { showBox.innerHTML = ''; } }; </script> </body>
使用promise封装
function jsonp(url,param={},paramName){ return new Promise((resolve,reject)=>{ if(typeof url != 'string'){ throw new Error('url必须为字符串') } url += '?' // 将param转为&拼接 for(let key in param){ url += `&${key}=${param[key]}` } //函数名需要加工(保持的函数名的唯一) let callbackName = 'fn' + Date.now() + Math.ceil(Math.random()*10) //加给对应的window window[callbackName] = resolve //resolve里面的参数会被then接收 这个resolve会被服务器自动调用并传入参数 //将参数名和回调函数名传入 url += `&${paramName}=${callbackName}` //创建一个script标签 let script = document.createElement('script') //指定对应的src地址 script.src = url //加给body document.body.appendChild(script) //script标签加载完毕 script.onload = function(){ this.remove() //将script标签删除 delete window[callbackName] //将对应的属性删除 } //script报错的时候 script.onerror = function(err){ reject('错误'+err) } }) }
测试代码:
<body> <input type="text" class="input" /> <ul class="showBox"></ul> <script src="./jsonpPromise.js"></script> <script> let inp = document.querySelector('.input'); let showBox = document.querySelector('.showBox'); inp.oninput = function () { let wd = this.value; if (wd) { jsonp( 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su', { wd, }, 'cb' ).then((res) => { showBox.innerHTML = ''; res.s.forEach((v) => { showBox.innerHTML += `<li>${v}</li>`; }); }); } else { showBox.innerHTML = ''; } }; </script> </body>