跨域 布满荆棘的人生 2021-08-13 18:05 410阅读 0赞 ### 跨域 ### * 同源政策 * 限制范围 * 举例 * * demo1 * demo2 * Cookie * iframe * * 1 片段识别符 * 2 window.name * 3 window.postMessage * 4 LocalStorage * AJAX * * JSONP * WebSocket * CORS * * \*\*简单请求的处理方式\*\* * \*\*非简单请求的处理方式\*\* # 同源政策 # **一个源的定义** > 如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源。 举个例子: 下表给出了相对http://a.xyz.com/dir/page.html同源检测的示例: <table> <thead> <tr> <th>URL</th> <th>结果</th> <th></th> </tr> </thead> <tbody> <tr> <td><code>http://a.xyz.com/dir2/other.html</code></td> <td>成功</td> <td></td> </tr> <tr> <td><code>http://a.xyz.com/dir/inner/another.html</code></td> <td>成功</td> <td></td> </tr> <tr> <td><code>https://a.xyz.com/secure.html</code></td> <td>失败</td> <td>不同协议 ( https和http )</td> </tr> <tr> <td><code>http://a.xyz.com:81/dir/etc.html</code></td> <td>失败</td> <td>不同端口 ( 81和80)</td> </tr> <tr> <td><code>http://a.opq.com/dir/other.html</code></td> <td>失败</td> <td>不同域名 ( xyz和opq)</td> </tr> </tbody> </table> **同源策略是什么** 同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。所以xyz.com下的js脚本采用ajax读取abc.com里面的文件数据是会被拒绝的。 同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。 设想这样一种情况:A网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取A网站的 Cookie,会发生什么? 很显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。 由此可见,"同源政策"是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。 **不受同源策略限制的** * 页面中的链接,重定向以及表单提交是不会受到同源策略限制的。 * 跨域资源的引入是可以的。但是js不能读写加载的内容。如嵌入到页面中的<script src="…"></script>,<img>,<link>,<iframe>等。 # 限制范围 # 随着互联网的发展,"同源政策"越来越严格。目前,如果非同源,共有三种行为受到限制。 * Cookie、LocalStorage 和 IndexDB 无法读取。 * DOM 无法获得。 * AJAX 请求不能发送。 # 举例 # ## demo1 ## urls.py urlpatterns = [ url(r'^abc/', views.abc), ] views.py def abc(request): return HttpResponse("rion") ## demo2 ## urls.py urlpatterns = [ url(r'^xyz/', views.xyz), ] views.py def xyz(request): return render(request, "xyz.html") xyz.html <!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>xyz</title> </head> <body> <button id="b1">点我</button> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script> <script> $("#b1").click(function () { $.ajax({ url: "http://127.0.0.1:8002/abc/", type: "get", success:function (res) { console.log(res); } }) }); </script> </body> </html> 现在,打开使用浏览器打开http://127.0.0.1:8000/xyz/,点击页面上的 ‘点我’ 按钮,会在console页面发现错误信息如下: ![在这里插入图片描述][20191203201156471.png] 为什么报错呢?因为同源策略限制跨域发送ajax请求。 接下来将介绍如何规避上面三种限制。 # Cookie # Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。但是,如果两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共享 Cookie。 举例来说,A网页是http://w1.example.com/a.html,B网页是http://w2.example.com/b.html,那么只要设置相同的document.domain,两个网页就可以共享Cookie。 document.domain = 'example.com'; 现在,A网页通过脚本设置一个 Cookie。 document.cookie = "test1=hello"; B网页就可以读到这个 Cookie。 var allCookie = document.cookie; 注意,这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 无法通过这种方法,规避同源政策,而要使用下文介绍的PostMessage API。 另外,服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如.example.com。 Set-Cookie: key=value; domain=.example.com; path=/ 这样的话,二级域名和三级域名不用做任何设置,都可以读取这个Cookie。 # iframe # 如果两个网页不同源,就无法拿到对方的DOM。典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。 比如,父窗口运行下面的命令,如果iframe窗口不是同源,就会报错。 document.getElementById("myIFrame").contentWindow.document // Uncaught DOMException: Blocked a frame from accessing a cross-origin frame. 上面命令中,父窗口想获取子窗口的DOM,因为跨源导致报错。 反之亦然,子窗口获取主窗口的DOM也会报错。 window.parent.document.body // 报错 如果两个窗口一级域名相同,只是二级域名不同,那么设置上一节介绍的document.domain属性,就可以规避同源政策,拿到DOM。 对于完全不同源的网站,目前有三种方法,可以解决跨域窗口的通信问题。 > * 片段识别符(fragment identifier) > * window.name > * 跨文档通信API(Cross-document messaging) ## 1 片段识别符 ## 片段标识符(fragment identifier)指的是,URL的\#号后面的部分,比如http://example.com/x.html\#fragment的\#fragment。如果只是改变片段标识符,页面不会重新刷新。 父窗口可以把信息,写入子窗口的片段标识符 var src = originURL + '#' + data; document.getElementById('myIFrame').src = src; 子窗口通过监听hashchange事件得到通知。 window.onhashchange = checkMessage; function checkMessage() { var message = window.location.hash; // ... } 同样的,子窗口也可以改变父窗口的片段标识符。 parent.location.href= target + "#" + hash; ## 2 window.name ## 浏览器窗口有window.name属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。 父窗口先打开一个子窗口,载入一个不同源的网页,该网页将信息写入window.name属性。 window.name = data; 然后,主窗口就可以读取子窗口的window.name了。 var data = document.getElementById('myFrame').contentWindow.name; 这种方法的优点是,window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。 ## 3 window.postMessage ## 上面两种方法都属于破解,HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。 这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。 举例来说,父窗口http://aaa.com向子窗口http://bbb.com发消息,调用postMessage方法就可以了。 var popup = window.open('http://aaa.com', 'title'); popup.postMessage('Hello World!', 'http://aaa.com'); postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为\*,表示不限制域名,向所有窗口发送。 子窗口向父窗口发送消息的写法类似。 window.opener.postMessage('Nice to see you', 'http://bbb.com'); 父窗口和子窗口都可以通过message事件,监听对方的消息。 window.addEventListener('message', function(e) { console.log(e.data); },false); message事件的事件对象event,提供以下三个属性。 > * event.source:发送消息的窗口 > * event.origin: 消息发向的网址 > * event.data: 消息内容 下面的例子是,子窗口通过event.source属性引用父窗口,然后发送消息。 window.addEventListener('message', receiveMessage); function receiveMessage(event) { event.source.postMessage('Nice to see you!', '*'); } event.origin属性可以过滤不是发给本窗口的消息。 window.addEventListener('message', receiveMessage); function receiveMessage(event) { if (event.origin !== 'http://bbb.com') return; if (event.data === 'Hello World') { event.source.postMessage('Hello', event.origin); } else { console.log(event.data); } } ## 4 LocalStorage ## 通过window.postMessage,读写其他窗口的 LocalStorage 也成为了可能。 下面是一个例子,主窗口写入iframe子窗口的localStorage。 window.onmessage = function(e) { if (e.origin !== 'http://bbb.com') { return; } var payload = JSON.parse(e.data); localStorage.setItem(payload.key, JSON.stringify(payload.data)); }; 上面代码中,子窗口将父窗口发来的消息,写入自己的LocalStorage。 父窗口发送消息的代码如下。 var win = document.getElementsByTagName('iframe')[0].contentWindow; var obj = { name: 'Jack' }; win.postMessage(JSON.stringify({ key: 'storage', data: obj}), 'http://bbb.com'); 加强版的子窗口接收消息的代码如下。 window.onmessage = function(e) { if (e.origin !== 'http://bbb.com') return; var payload = JSON.parse(e.data); switch (payload.method) { case 'set': localStorage.setItem(payload.key, JSON.stringify(payload.data)); break; case 'get': var parent = window.parent; var data = localStorage.getItem(payload.key); parent.postMessage(data, 'http://aaa.com'); break; case 'remove': localStorage.removeItem(payload.key); break; } }; 加强版的父窗口发送消息代码如下。 var win = document.getElementsByTagName('iframe')[0].contentWindow; var obj = { name: 'Jack' }; // 存入对象 win.postMessage(JSON.stringify({ key: 'storage', method: 'set', data: obj}), 'http://bbb.com'); // 读取对象 win.postMessage(JSON.stringify({ key: 'storage', method: "get"}), "*"); window.onmessage = function(e) { if (e.origin != 'http://aaa.com') return; // "Jack" console.log(JSON.parse(e.data).name); }; # AJAX # 同源政策规定,AJAX请求只能发给同源的网址,否则就报错。 除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制。 > * JSONP > * WebSocket > * CORS ## JSONP ## JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。 它的基本思想是,网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。 首先,网页动态插入<script>元素,由它向跨源网址发出请求。 function addScriptTag(src) { var script = document.createElement('script'); script.setAttribute("type","text/javascript"); script.src = src; document.body.appendChild(script); } window.onload = function () { addScriptTag('http://example.com/ip?callback=foo'); } function foo(data) { console.log('Your public IP address is: ' + data.ip); }; 上面代码通过动态添加<script>元素,向服务器example.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于JSONP是必需的。 服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。 foo({ "ip": "8.8.8.8" }); 由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤。 ## WebSocket ## WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。 下面是一个例子,浏览器发出的WebSocket请求的头信息 GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com 上面代码中,有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。 正是因为有了Origin这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。 HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat ## CORS ## CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。它允许浏览器向跨源服务器发出XMLHttpRequest请求,相比JSONP只能发GET请求,CORS允许任何类型的请求。 **CORS简介** CORS需要浏览器和服务器同时支持。目前基本上主流的浏览器都支持CORS。所以只要后端服务支持CORS,就能够实现跨域。 **简单请求和非简单请求介绍** 浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。 一个请求需要同时满足以下两大条件才属于简单请求。 (1) 请求方法是以下三种方法之一: HEAD GET POST (2)HTTP的头信息不超出以下几种字段: Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain ### **简单请求的处理方式** ### 在跨域场景下,当浏览器发送简单请求时,浏览器会自动在请求头中添加表明请求来源的 Origin 字段。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmdfemhhb18_size_16_color_FFFFFF_t_70] 我们的后端程序只需要在返回的响应头中加上 Access-Control-Allow-Origin 字段,并且把该字段的值设置为 跨域请求的来源地址或简单的设置为 \* 就可以了。 @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletResponse response = (HttpServletResponse) res; response.setHeader("Access-Control-Allow-Origin","*"); // doSomething } ### **非简单请求的处理方式** ### 我们开发中常用到的那些请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json的都是非简单请求。 对于非简单请求,浏览器通常都会在请求之前发送一次 OPTIONS 预检 请求。该请求会像后端服务询问是否允许从当前源发送请求并且询问允许的 **请求方法** 和 **请求头字段**。 @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletResponse response = (HttpServletResponse) res; response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "x-requested-with"); // doSomething } 举个例子: 我们前端使用axios向后端发送PUT请求,结果: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmdfemhhb18_size_16_color_FFFFFF_t_70 1] 看看发送的具体请求: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmdfemhhb18_size_16_color_FFFFFF_t_70 2] 解决办法也很简单,我们可以在后端简单的给响应对象添加上 常用请求方法(PUT、DELETE)的支持就可以了。 [20191203201156471.png]: /images/20210813/e4357317cc124eda82c8ef8c8d8ea824.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmdfemhhb18_size_16_color_FFFFFF_t_70]: /images/20210813/3232f841e8da4ff1b35b115942cfd7af.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmdfemhhb18_size_16_color_FFFFFF_t_70 1]: /images/20210813/c07769c5c34541efb4e9f59f5f298a25.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmdfemhhb18_size_16_color_FFFFFF_t_70 2]: /images/20210813/71492a7aa09749bd87861e1b246ce2db.png
相关 跨域、跨域问题 跨域(CORS)是一种允许当前域(domain)的资源(比如html/js/web service)被其他域(domain)的脚本请求访问的机制,通常由于同域安全策略(the 朴灿烈づ我的快乐病毒、/ 2023年09月27日 00:59/ 0 赞/ 195 阅读
相关 跨域与跨域访问 转自:http://blog.csdn.net/notechsolution/article/details/50394391 什么是跨域 跨域是指从一个域名的网页 淡淡的烟草味﹌/ 2022年06月14日 09:26/ 0 赞/ 395 阅读
相关 跨域 跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。 所谓同源是指,域名,协议,端口均相同 ![20171 冷不防/ 2022年06月03日 02:50/ 0 赞/ 253 阅读
相关 跨域 jsonp: $(function()\{ $.ajax(\{ async: false, type: "GET", dataType 绝地灬酷狼/ 2022年05月29日 01:29/ 0 赞/ 263 阅读
相关 跨域 一、什么是域 英文 domain,是计算机专用词汇,是指Windows网络中独立运行的单位---百度百科 域是一个有安全边界的计算机集合,在同一个 ゝ一纸荒年。/ 2022年05月19日 11:41/ 0 赞/ 386 阅读
相关 跨域 CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案 JSONP只支持GET请求,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。 墨蓝/ 2022年02月28日 07:24/ 0 赞/ 651 阅读
相关 跨域 1、同源: 同源策略是浏览器的一种安全策略,所谓同源是指,域名,协议,端口号完全相同 1.1目的:保护用户信息安全 1.2限制:cookie、localSto Myth丶恋晨/ 2022年01月20日 04:35/ 0 赞/ 328 阅读
相关 跨域 2018-07-11 发布 [不要再问我跨域的问题了][Link 1] [javascript][] [前端][Link 2] 野性酷女/ 2021年12月18日 14:59/ 0 赞/ 342 阅读
相关 跨域 引言: 简单说跨域就是A网站调用B网站的程序,获取对应的数据;但是受制于同源策略,这样是不可行的,但是有时候我们有时候又需要这样的情境进行使用 复制代码 同 超、凢脫俗/ 2021年07月25日 23:50/ 0 赞/ 525 阅读
还没有评论,来说两句吧...