关于同源策略:前端面试每日-31-第563天

明天的知识点 (2020.10.30) —— 第563天 (我也要出题)[html] 间接应用b标签和应用css的font-weight有什么区别?[css] 如何垂直居中一个浮动的元素?[js] map和forEach有什么区别?[软技能] 如果没有同源限度将会怎么样?《论语》,曾子曰:“吾日三省吾身”(我每天屡次检查本人)。前端面试每日3+1题,以面试题来驱动学习,每天提高一点!让致力成为一种习惯,让奋斗成为一种享受!置信 保持 的力量!!!欢送在 Issues 和敌人们一起探讨学习! 我的项目地址:前端面试每日3+1【举荐】欢送跟 jsliang 一起折腾前端,零碎整顿前端常识,目前正在折腾 LeetCode,打算买通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个Star, 同时欢送微信扫码关注 前端剑解 公众号,并退出 “前端学习每日3+1” 微信群互相交换(点击公众号的菜单:交换)。 学习不打烊,充电加油只为遇到更好的本人,365天无节假日,每天早上5点纯手工公布面试题(死磕本人,愉悦大家)。心愿大家在这虚夸的前端圈里,放弃沉着,保持每天花20分钟来学习与思考。在这变幻无穷,类库层出不穷的前端,倡议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢送大家到Issues交换,激励PR,感激Star,大家有啥好的倡议能够加我微信一起交换探讨!心愿大家每日去学习与思考,这才达到来这里的目标!!!(不要为了谁而来,要为本人而来!)交换探讨欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个[Star]

October 30, 2020 · 1 min · jiezi

怎样与 CORS 和 cookie 打交道

翻译:疯狂的技术宅本文首发微信公众号:jingchengyideng欢迎关注,每天都给你推送新鲜的前端技术文章前言CORS 与 cookie 在前端是个非常重要的问题,不过在大多数情况下,因为前后端的 domain 一般是相同的,所以很少去关心这些问题。或者只是要求后端设置 Access-Control-Allow-Origin: * 就行了,很少去了解背后运作的机制。针对这个问题,MDN 上有非常详细的解释,所以这篇文章主要在于整理重点和实际操作时经常出现的问题。同源策略(same-origin policy)为了防止 javascript 在网页上随意撒野,同源策略规定了某些特定的资源,代码必须在同源的情况下才可以存取。什么是同源呢?一份 document 的来源,由 protocol、host 和 port 来定义。也就是说如果文件1来自http://kalan.com,而文件2来自于 https://kalan.com 他们就不算是同源。那如果是子域名呢?像是 https://api.foobar.com 和 https://app.foobar.com。因为他们的 host 不同,所以也不算同一个源。而有些资源是本来就能够通过跨来源取得的:<img /><video />, <audio /><iframe />:可以通过定义 header 来防止他人嵌入通过 <link rel=“stylesheet” href /> 载入的CSS脚本<script src="" /> 载入的 Javascript通过代码发出的跨源请求则会受到同源策略的限制(如Fetch,XHR)。很显然,这样的规定太过严格了。如果都要限制在同源策略下的话,前后端开发会难以进行,也没办法用 XHR 的方式套用其他 SDK 的 API。也因此出现了 CORS( Cross-Origin Resource Sharing)的机制。CORS(跨源资源共享)很多人都觉得 CORS 是前端才需要具备的知识。不过 CORS 通常需要后端设定相关的 HTTP 头,并且了解背后的含义才有办法正确运作。那么跨来源请求是怎么运作的呢?主要是由两个 Header 来做相对的存取控制:请求当中的 Origin 和响应中的 Access-Control-Allow-Origin。只要发送请求时的 Origin 和响应头中 Access-Control-Allow-Origin 的值相同,或是 Access-Control-Allow-Origin: (代表允许任何域存取资源),此时就会放宽 CORS 的限制,允许存取跨域资源。如果不符合 CORS 策略的话,会显示下列信息:如果你尝试去读取回传的物件,还会得到警告。首先,如果我们按照提示中所说的,将 fetch mode 改成 no-cors 会发生什么事呢?的确,我们把烦人的错误信息给处理掉了,但是情况似乎并没有变好。no-cors并不是灵丹妙药,就算用了这个模式,CORS 也不会因此就打开大门,也就是你的请求并不会成功发出。也因此出现了 SyntaxError: Unexpected end of input 这个错误。这个模式通常是跟Service Worker搭配使用的。从上面这个实验当中可知,要解除CORS的封印只有一招,就是在服务器端加上正确的 Control-Access-Allow-Origin(host 必须跟原来相同或是)。另外,CORS 这个机制只会运作在 javascript 送出 XHR 或 fetch 时,一般 curl 或 postman 并没有这个机制,所以也因此常常在测试 API 端点时会忽略这件事,导致前后端在测试 API 时发生出入。有些跨来源请求不会发生 preflight,而有些请求则会,MDN上写的清清楚楚:必须是 GET,HEAD,POST 中的一种方法除了 user-agent 自动设置的 header 和特定的 header 之外,不包含其他 header 。可接受的header若有 Content-Type(注意是请求头,不是响应头),则必须是下列的值:application/x-www-form-encoded,text/plain,multipart/form-data也就是说如果不满足以上条件的话,就会发出 preflight 请求。我们试着把 Content-Type 改为 application/json 来测试一下(不能为 application/x-www-form-encoded,text/plain,multipart/form-data)。Preflight所谓的 preflight 就是请求会先用 HTTP 的 OPTION 方法去另外一个域敲门,确认没问题后才会送出真正的请求。一旦触发了这个条件,事情就会变得麻烦得多。必须加入一个 OPTIONS 的相同 api endpoint,并且设定 Access-Control-Allow-Origin 来符合 CORS 条件必须加入Access-Control-Allow-Headers,且必须包含所有不在条件内 header,否则无法通过。如果没有通过 preflight check 的话,会得到错误信息如下:Access to fetch at ‘http://localhost:3001/trigger-preflight’ from origin ‘http://localhost:3000’ has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.或是你没有在 OPTIONS 的响应头里加上 Access-Control-Allow-Origin:Access to fetch at ‘http://localhost:3001/trigger-preflight’ from origin ‘http://localhost:3000’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ’no-cors’ to fetch the resource with CORS disabled.如果成功的话,你会看到 network 里有两个请求,一个是 OPTIONS,另一个则是真正的请求。上图为 OPTION,下图为GET如果我们加上一个自制的头呢?根据MDN所定义的条件,也应该触发预检请求才对,我们加上一个X-Access-Token看看会发生什么事。的确无法通过preflight,如果要通过的话,必须再把 X-Access-Token 加入 Access-Control-Allow-Headers 中。附带身份验证的请求cookie 并不能跨域传递,也就是说不同 origin 来的 cookie 没办法互相传递及存取,不然就天下大乱了。不过如果你在 a 域送出了 b 域的请求,且 b 域回传了 cookie 的信息,那么在 a 域会以 b 域的形式储存一份cookie,如果没有设定 withCredentials 或是 credentials: ‘include’ 的话,就算服务器回传了 Set-Cookie,一样不会被写入。如下图:服务器回传Set-Cookie没有写入浏览器中在一般情况下如果再使用 b 域的 API,cookie 是不会自动被送出去的。这个情况下,你必须在 XHR 设定 withCredentials 或是 fetch 的选项中设置 { credentials: ‘include’ },因为这也是一个跨域请求,所以也必须按照 CORS 条件加入 Access-Control-Allow-Origin为了避免安全性的问题,器浏览还有规定 Access-Control-Allow-Origin不能为*。Access to fetch at ‘http://localhost:3001/cookie’ from origin ‘http://localhost:3000’ has been blocked by CORS policy: The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’.不过仅仅这样还是不够,器浏览会自动拒绝没有 Access-Control-Allow-Credentials 的响应,所以如果要将身份信息传到跨域的服务器中,必须额外加上 Access-Control-Allow-Credentials: true。如果这些都设定成功,应该会像下图这样,在 Request Cookie可以看到 cookie 被成功送出。Request Cookies 里有个 jack!好吧,如果你成功设定了这些东西,但还是有可能没办法把 cookie 送到服务器。那有可能会是以下几种情况:1.用户禁用了此域的 cookie可能使用者把你加入了黑名单,导致 cookie 无法成功送出解决方法:改域检讨自己为什么被用户封锁2.用户阻止了所有外部网站的cookie在Safari 中有时会开启“阻止所有Cookie”这一选项,这在调试时会让你尝到不少苦头。后记要处理 CORS 是件吃力不讨好的事情,尤其是有时在跑 CI/CD之前忘记加上 Access-Control-Allow-Origin 或是 Access-Control-Allow-Credentials,那么部署可能又是一天以后的事了。这次把一些常见的问题整理起来,希望以后如果再有类似的情形可以知道怎么处理。最后附上源代码。参考文章https://developer.mozilla.org…本文首发微信公众号:jingchengyideng欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章欢迎继续阅读本专栏其它高赞文章:12个令人惊叹的CSS实验项目世界顶级公司的前端面试都问些什么CSS Flexbox 可视化手册过节很无聊?还是用 JavaScript 写一个脑力小游戏吧!从设计者的角度看 ReactCSS粘性定位是怎样工作的一步步教你用HTML5 SVG实现动画效果程序员30岁前月薪达不到30K,该何去何从7个开放式的前端面试题React 教程:快速上手指南 ...

April 3, 2019 · 2 min · jiezi

一文 | 跨域及其解决方案

一文系列企图通过一篇简短的文章来梳理一个知识点,在杂碎的时间片段中给自己带来一点点提升。为什么有跨域这个问题简单的说,是因为浏览器的同源策略。同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。(引用于MDN定义)如果两个链接的协议、域名、端口都一致,那么这两个URL同源,否则不同源。假设A站点的链接为https://news.a.com/www/index.html,B站点为下列链接时其同源检测如下表URL结果原因https://news.a.com/www/.html同源 https://news.a.com/mmm/index.html同源 https://music.a.com/www/index.html不同源域名不一致http://news.a.com/www/index.html不同源协议不一致https://news.a.com:6666/www/index.html不同源端口不一致同源策略分为以下两种:DOM同源策略,禁止对不同源的DOM元素进行操作。XHR同源策略,禁止使用XHR对象向不同源的服务器发起请求。举个栗子,Jarry登陆了A站点准备购物,与此同时Jarry正在B站点网上冲浪。如果没有同源策略,那么B站点的脚本可以轻轻松松的修改A站点的DOM结构或者向A站点的服务器发起不恰当的请求,导致存在安全隐患。IE浏览器有两个意外:授信范围:两个相互之间高度互信的域名,不受同源策略的限制。端口:IE没有将端口号加入到同源策略的组成部分之中。什么是跨域当A站点与B站点不同源(只要协议、域名、端口三者其一不一致)时,A站点无法获取到B站点的服务或者数据,此时就产生了跨域。上图中,站点https://www.jarrychung.com企图向不同源站点https://www.baidu.com发起GET请求,导致报错。如何解决跨域1 JSONP需要服务端支持。JSON是一种常用的数据交换格式,而JSONP是JSON的一种使用模式,可以通过这种模式来进行跨域获取数据。重要的是,JSONP使用简便,没有兼容性问题。同源策略下,不同源的站点无法相互获取到数据,但img/iframe/script标签是个例外,这些标签可以通过src属性获取到不同源的服务器。当正常的请求一个JSON数据时,服务端会返回JSON格式的数据。当使用JSONP模式发送请求时,服务端返回的是一段可执行的JavaScript代码。// 举个例子// 正常请求服务器(https://news.a.com/news?id=666)时,数据如下:{“id”: 666,“text”:“Jarry Chung”}// JSONP模式请求(https://news.a.com/news?id=666?callback=fn)时,数据如下:fn({“id”: 666,“text”:“Jarry Chung”})// 然后使用回调函数便可以处理获得的数据注意:JSONP只支持GET请求,服务端可能在JSONP响应中夹带恶意代码,判断是否请求成功是困难的。2 跨域资源共享(CORS)需要服务端支持。CORS经常被称为现代化版本的JSONP,能够发起所有种类的HTTP请求,以及拥有良好的错误处理。跨源资源共享标准的工作原理是添加自定义的HTTP头部,允许服务器描述允许使用Web浏览器读取该信息的起源集。此外,对于可能对服务器数据产生副作用的HTTP请求方法,规范要求浏览器“预检”请求,从而请求支持的方法。服务器使用HTTP OPTIONS请求方法,然后,在服务器“批准”后,使用实际的HTTP请求方法发送实际请求。服务器还可以通知客户端是否让“凭据”(包括Cookie和HTTP认证数据)与请求一起发送(翻译自MDN)。CORS的基本思想是使用自定义的HTTP头部允许服务端和浏览器互相认识,从而让服务端决定是否允许请求以及响应。Access-Control-Allow-Origin:指定授权访问的域Access-Control-Allow-Methods:授权请求的方法(GET, POST, PUT, DELETE,OPTIONS等)// 举个例子var exp = require(’express’);var app = exp();app.all(’’, function(req, res, next) { // 设置允许的源 res.header(“Access-Control-Allow-Origin”, “”); // 设置允许的HTTP请求方式 res.header(“Access-Control-Allow-Methods”,“PUT,POST,GET,DELETE,OPTIONS”); res.header(“Access-Control-Allow-Headers”, “X-Requested-With”); res.header(“Content-Type”, “application/json;charset=utf-8”); next();});app.get(’/user/:id/:pw’, function(req, res) { res.send({id:req.params.id, password: req.params.pw});});app.listen(8000);注意:是最为推荐的方案,但古董级浏览器不支持CORS,如IE8以下的浏览器。3 Nginx反向代理主要在服务端上实现。浏览器有同源策略,但是服务端没有这个限制,因此可以将请求发送给反向代理服务器,由服务器去请求数据,然后再将数据返回给前端。而前端几乎不需要做任何处理。server { # 监听80端口,可以改成其他端口 listen 80; # 当前服务的域名 server_name www.a.com; location / { proxy_pass http://www.a.com:81; proxy_redirect default; } # 添加访问目录为/api的代理配置 # 目录为/api开头的请求将被转发到82端口 # 还记得吗,端口不同也是不同源 location /apis { rewrite ^/apis/(.)$ /$1 break; proxy_pass http://www.a.com:82; }4 window.name + iframe在浏览器实现。window.name的值是当前窗口的名字,要注意的是每个iframe都有包裹它的window,而这个window是top window的子窗口,因此它自然也有window.name的属性。window.name属性,如果没有被修改,那么其值在不同的页面(甚至不同域名)加载后依旧存在。另外,其值大小通常可达到2MB。其思想为:在一个页面中内嵌一个iframe标签,由这个iframe进行获取数据,将获取到的数据赋值给window.name属性,然后由页面获取该属性的值。既巧妙的绕过了同源策略,同时该操作也是安全的。但这里有一个问题,即页面和该页面下的iframe src不同源的话,这个页面是无法操作iframe的,因此导致取不到name值。name属性的特性在这时候就很好用了,当前页设置的值, 在页面重新加载(非同域也可以)后, 只要没有被修改,值依然不变。可以让iframe的location指向为与页面相同的域,等iframe加载完后页面就可以取到name值了。<body> <script type=“text/javascript”> // 代码参考自:https://www.cnblogs.com/zichi/p/4620656.html function crossDomain(url, fn) { iframe = document.createElement(‘iframe’); iframe.style.display = ’none’; var state = 0; iframe.onload = function() { if(state === 1) { // 处理数据 fn(iframe.contentWindow.name); // 清楚痕迹 iframe.contentWindow.document.write(’’); iframe.contentWindow.close(); document.body.removeChild(iframe); } else if(state === 0) { state = 1; // proxy.html为与页面同级的空白页面 iframe.contentWindow.location = ‘http://localhost:81/proxy.html’; } }; iframe.src = url; document.body.appendChild(iframe); } // 调用 // 服务器地址 var url = ‘http://localhost:82/data.php’; // 处理数据 data就是window.name的值(string) crossDomain(url, function(data) { var data = JSON.parse(iframe.contentWindow.name); console.log(data); }); </script></body>5 location.hash + iframe主要在浏览器实现,需要服务端支持。在https://www.a.com/news#JarryChungIsSoCool这个URL中,location.hash的值为JarryChungIsSoCool。因为改变hash值不会导致刷新页面,因此可以利用location.hash属性来传递数据。缺点是数据容量以及类型受到限制、数据内容直接暴露出来。其思想为:若index页面要获取不同源服务器的数据,那么动态插入一个iframe,将iframe的src属性指向该服务器地址。由于同源策略,此时top window和包裹这个iframe的子窗口仍是无法通信的,因此改变子窗口的路径,将数据当作hash值添加到改变后的路径,然后就能够进行通信(这一点与利用window.name跨域的原理几乎一致),能够通信后可以在iframe中将页面的地址改变,将数据加在index页面地址的hash值上。index页面监听地址的hash值变化便能够取得数据。6 document.domain + iframe在浏览器实现。该方案适用于主域名一致,子域名不一致的情况。两个页面使用JavaScript将document.domain设置为相同主域名,从而实现跨域。<!– 主页面 a.html –><iframe src=“http://child.domain.com/b.html"></iframe><script> document.domain = ‘domain.com’; var user = ‘Jarry Chung’;</script><!– 子页面 b.html –><script> document.domain = ‘domain.com’; // 获取父窗口中 user 变量 alert(window.parent.user); // ‘Jarry Chung’</script>7 postMessage()在浏览器实现。postMessage()是HTML5新增的方法,可以实现跨文本档通信、多窗口通信、跨域通信。示意图如下:index.html将需要的数据请求发送给iframe或者另一个页面,iframe或另一个页面监听到message后响应,取得数据后利用postMessage()接口将数据返回给index.html。postMessage()有两个参数:data:需要传递的数据,HTML5规范中该参数的类型可以是JS的任意基本类型或者可复制的对象,但有部分浏览器只支持传递字符串,因此可能需要将该值处理成字符串后再传递。origin:字符串参数,指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,可以不写。postMessage()方法只会将message传递给指定窗口,当然如果愿意也可以把参数设置为,这样可以传递给任意窗口。8 WebSocket协议需要服务端支持。WebSocket协议是HTML5一种新的协议,实现了浏览器与服务器全双工通信,同时允许跨域通讯。用法如下: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.close();};// 监听关闭连接ws.onclose = function (evt) { console.log(‘Connection closed.’);};写在最后个人经验表示在实战中碰见跨域的情况并不多,但是如果碰见了往往都会掉坑里面。作为一线开发者,做好知识储备是在需要的时候迅速解决问题的必要条件。 ...

January 21, 2019 · 2 min · jiezi