关于跨域:前后端解决跨域问题

跨域的容许次要有服务器端管制。服务器端通过在响应的header中设置Access-Control-Origin及相干一系列参数,提供跨域拜访的容许策略 设置响应头中的参数来容许跨域域申请: Access-Control-Allow-CredentialsAccess-Control-Allow-Origin 标识容许跨域的申请有哪些在POM文件中引入依赖<!-- 解决跨域问题所需依赖 --><dependency> <groupId>com.thetransactioncompany</groupId> <artifactId>cors-filter</artifactId> <version>2.5</version></dependency>在web.xml中配置跨域filter<!-- 配置跨域过滤器 --><filter> <filter-name>corsFilter</filter-name> <filter-class>cors-filter</filter-class></filter><filter-mapping> <filter-name>corsFilter</filter-name> <url-pattern>/*</url-pattern></filter-mapping>

January 28, 2023 · 1 min · jiezi

关于跨域:CORS跨域

01、索引https://waterflow.link/articles/1665656761584 1、为什么跨域跨域资源共享 (CORS) 是一种基于 HTTP 标头的机制,它容许服务器批示除其本身之外的任何起源(域、计划或端口),浏览器应容许从中加载资源。 CORS 还依赖于一种机制,浏览器通过该机制向托管跨域资源的服务器收回“预检”申请,以查看服务器是否容许理论申请。 在该预检中,浏览器发送批示 HTTP 办法的标头和将在理论申请中应用的标头。 跨域申请的示例:从 https://a.com 提供的前端 JavaScript 代码应用 ajax 向 https://b.com/data.json 发出请求。 出于平安起因,浏览器限度从脚本发动的跨域 HTTP 申请。 例如,ajax 和 申请的 API 遵循同源策略。 这意味着应用这些 API 的 Web 应用程序只能从加载应用程序的同一起源申请资源,除非来自其余起源的响应蕴含正确的 CORS 标头。 2、跨域的规定CORS规范通过增加新的 HTTP 头来工作,服务器能够返回容许哪些起源从 Web 浏览器读取该信息。此外,对于可能对服务器数据造成副作用的 HTTP 办法(特地是 GET 以外的 HTTP办法 ,或具备某些 MIME 类型的 POST),标准要求浏览器“预检”申请(先发送OPTIONS申请),而后在服务器“容许”后发送理论申请。 CORS 失败会导致谬误,但出于平安起因,JavaScript 无奈取得无关谬误的详细信息。所有代码都晓得产生了谬误。确定具体出了什么问题的惟一办法是查看浏览器的控制台以获取详细信息。 3、跨域场景1、不会触发预检简略的申请不会触发预检,那什么是简略的申请呢? 首先申请办法必须是GET、HEAD、POST其中之一除了客户端主动设置的header头(比方:Connection、User-Agent等),只能设置如下的这些header头 AcceptAccept-LanguageContent-LanguageContent-Type:只容许application/x-www-form-urlencoded、multipart/form-data、text/plain类型Range:只能是一些简略的header头,比方bytes=256-或者bytes=127-255上面是一个简略申请的流程: 首先客户端会向a.com会向服务端b.com发送申请,并带上源域名。作为响应,服务器返回一个带有 Access-Control-Allow-Origin: * 的 Access-Control-Allow-Origin 标头,这意味着该资源能够被任何起源拜访。(个别容许某个域或者某几个域,能够用正则)2、会触发预检和简略申请不同,处于平安思考,会先向“预检”申请,浏览器首先应用 OPTIONS 办法向另一个源上的资源发送 HTTP 申请,以确定理论申请是否能够平安发送。 上面是预检申请的流程: 首先客户端header头曾经不合乎简略申请的header,这时会触发预检。客户端发送OPTIONS申请,以获取容许跨域的header头,会返回204 No Content的状态码预检胜利之后,客户端会发送失常的POST申请3、nginx跨域配置留神:如果nginx设置跨域反复,客户端console也会提醒反复跨域 ...

October 13, 2022 · 1 min · jiezi

关于跨域:前端微服务跨域配置解决办法devServer为例

前言Nginx: 在上一篇我提到的跨域配置是正式上线的时候应用nginx做为配置的参考。Webpack: 而咱们更多的时候是在开发阶段就须要通过跨域进行联合开发各个子利用局部性能DevServer配置解决跨域子利用动态资源跨域 在webpack.config.js或者vue.config.js找到devServer属性;配置如下: devServer: { headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': true, 'Access-Control-Allow-Methods': 'GET,POST,OPTIONS,PUT,DELETE,FETCH', 'Access-Control-Allow-Headers': 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' }, }子利用接口代理跨域 在webpack.config.js或者vue.config.js找到proxy属性;配置如下: devServer: { proxy: { '/api': { target: 'http://xxx.xx.xx.x', changeOrigin: true, pathRewrite: { '^/api': '' }, onProxyRes: function (proxyRes, req, res) { if (req.method === 'OPTIONS') { proxyRes.headers['Access-Control-Allow-Origin'] = req.headers.origin || '*' proxyRes.headers['Access-Control-Allow-Credentials'] = true proxyRes.headers['Access-Control-Allow-Methods'] = 'GET,POST,OPTIONS,PUT,DELETE,FETCH' proxyRes.headers['Access-Control-Allow-Headers'] = 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' proxyRes.statusCode = 204 } else { proxyRes.headers['Access-Control-Allow-Origin'] = req.headers.origin || '*' proxyRes.headers['Access-Control-Allow-Credentials'] = true } } } }}也能够通过一些其余配置实现:这样就能在开发阶段子利用能够间接在父利用内随便拜访了。不太懂得敌人们能够。留言问我。 ...

September 26, 2022 · 1 min · jiezi

关于跨域:一文搞懂│什么是跨域如何解决跨域

什么是跨域域: 是指浏览器不能执行其余网站的脚本跨域: 它是由浏览器的 同源策略 造成的,是浏览器对 JavaScript 施行的平安限度,所谓同源(即指在同一个域)就是两个页面具备雷同的协定 protocol,主机 host 和端口号 port 则就会造成 跨域 跨域场景场景的跨域场景有哪些,请参考下表以后url申请url是否跨域起因http://www.autofelix.cnhttp://www.autofelix.cn/api.php否协定/域名/端口都雷同http://www.autofelix.cnhttps://www.autofelix.cn/api.php是协定不同http://www.autofelix.cnhttp://www.rabbit.cn是主域名不同http://www.autofelix.cnhttp://api.autofelix.cn是子域名不同http://www.autofelix.cn:80http://www.autofelix.cn:8080是端口不同 解决跨域的四种形式nginx的反向代理应用 nginx 反向代理实现跨域,是最简略的跨域形式只须要批改 nginx 的配置即可解决跨域问题,反对所有浏览器,反对session,不须要批改任何代码,并且不会影响服务器性能// nginx配置server { listen 81; server_name www.domain1.com; location / { proxy_pass http://www.domain2.com:8080; #反向代理 proxy_cookie_domain www.domain2.com www.domain1.com; #批改cookie里域名 index index.html index.htm; # 当用webpack-dev-server等中间件代理接口拜访nignx时,此时无浏览器参加,故没有同源限度,上面的跨域配置可不启用 add_header Access-Control-Allow-Origin http://www.domain1.com; #以后端只跨域不带cookie时,可为* add_header Access-Control-Allow-Credentials true; }}jsonp申请jsonp 是服务器与客户端跨源通信的罕用办法。最大特点就是简略实用,兼容性好 兼容低版本IE,毛病是只反对 get 申请,不反对 post 申请原理时网页通过增加一个 <script> 元素,向服务器申请 json 数据,服务器收到申请后,将数据放在一个指定名字的回调函数的参数地位传回来//jquery实现<script>$.getJSON('http://autofelix.com/api.php&callback=?', function(res) { // 解决取得的数据 console.log(res)});</script>后端语言代理能够通过一种没有跨域限度的语言直达一下,通过后端语言去申请资源,而后再返回数据比方 http://www.autofelix.cn 须要调用 http://api.autofelix.cn/userinfo 去获取用户数据,因为子域名不同,会有跨域限度能够先申请 http://www.autofelix.cn 下的 php 文件,比方 http://www.autofelix.cn/api.php,而后再通过该 php 文件返回数据// api.php 文件中的代码public function getCurl($url, $timeout = 5){ $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); $result = curl_exec($ch); curl_close($ch); return $result;}$result = getCurl('http://api.autofelix.cn/userinfo');return $result;后端语言的设置次要通过后端语言被动设置跨域申请,这里以 php 作为案例// 容许所有域名拜访header('Access-Control-Allow-Origin: *');// 容许单个域名拜访header('Access-Control-Allow-Origin: https://autofelix.com');// 容许多个自定义域名拜访static public $originarr = [ 'https://autofelix.com', 'https://baidu.com', 'https://csdn.net',];// 获取以后跨域域名$origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '';if (in_array($origin, self::$originarr)) { // 容许 $originarr 数组内的 域名跨域拜访 header('Access-Control-Allow-Origin:' . $origin); // 响应类型 header('Access-Control-Allow-Methods:POST,GET'); // 带 cookie 的跨域拜访 header('Access-Control-Allow-Credentials: true'); // 响应头设置 header('Access-Control-Allow-Headers:x-requested-with,Content-Type,X-CSRF-Token');}

July 15, 2022 · 1 min · jiezi

关于跨域:后端工程师的跨域之旅

跨域,对后端工程师来说,堪称既相熟又生疏。 这两个月我以架构师的角色参加一款教育产品的孵化,有了一段难忘的跨域之旅。 写这篇文章,我想分享我在跨域这个知识点的经验和思考,心愿对大家有所启发。 1 遇见跨域产品有多端:机构端,局方端 ,家长端等 。每端都有独立的域名,有的是在PC上拜访,有的是通过微信公众号来拜访,有的是扫码后H5展示。 接入层调用的接口域名对立应用 api.training.com这个独立的域名,通过Nginx来配置申请转发。 通常,咱们提到的跨域指:CORS。 CORS是一个W3C规范,全称是"跨域资源共享"(Cross-origin resource sharing), 它须要浏览器和服务器同时反对他,容许浏览器向跨源服务器发送XMLHttpRequest申请,从而克服 AJAX 只能同源应用的限度。 那么如何定义同源呢?咱们先看下一个典型的网站的地址: 同源是指:协定、域名、端口号完全相同。 下表给出了与 URL http://www.training.com/dir/page.html 的源进行比照的示例: 当用户通过浏览器拜访利用(http://admin.training.com)时,调用接口的域名非同源域名(http://api.training.com),这是不言而喻的跨域场景。 2 CORS详解跨域资源共享规范新增了一组 HTTP 首部字段,容许服务器申明哪些源站通过浏览器有权限拜访哪些资源。 标准要求,对那些可能对服务器数据产生副作用的 HTTP 申请办法(特地是 GET 以外的 HTTP 申请,或者搭配某些 MIME 类型的 POST 申请),浏览器必须首先应用 OPTIONS 办法发动一个预检申请(preflight request),从而获知服务端是否容许该跨域申请。 服务器确认容许之后,才发动理论的 HTTP 申请。在预检申请的返回中,服务器端也能够告诉客户端,是否须要携带身份凭证(包含 Cookies 和 HTTP 认证相干数据)。 2.1 简略申请当申请同时满足如下条件时,CORS验证机制会应用简略申请, 否则CORS验证机制会应用预检申请。 应用GET、POST、HEAD其中一种办法;只应用了如下的平安首部字段,不得人为设置其余首部字段; AcceptAccept-LanguageContent-LanguageContent-Type 仅限三种之一:text/plain,multipart/form-data,application/x-www-form-urlencoded:HTML头部 header field字段:DPR、Download、Save-Data、Viewport-Width、WIdth申请中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象能够应用 XMLHttpRequest.upload 属性拜访;申请中没有应用 ReadableStream 对象。简略申请模式,浏览器间接发送跨域申请,并在申请头中携带Origin的头,表明这是一个跨域的申请。 服务器端接到申请后,会依据本人的跨域规定,通过Access-Control-Allow-Origin和Access-Control-Allow-Methods响应头,来返回验证后果。 ...

January 4, 2022 · 3 min · jiezi

关于跨域:SharedArrayBuffer-及其跨域下的使用

什么是跨域跨域问题是前端开发人员背后绕不过来的问题。所谓跨域,艰深点表白,就是以后站点,申请或加载了其余域名的资源。比方以后 app.a.com 申请了 file.b.com 的资源,就产生了跨域资源的加载。 浏览器在渲染以后页面时,校验网页申请的资源是否能够平安的在以后页面应用。比方: 是同站点下的资源,比方 app.a.com/logo.png,能够间接应用如果是同域名下的资源,比方 file.a.com/logo.png, 这时会校验文件的响应头,看有没有配置 Access-Control-Allow-* 相干的访问控制参数如果是 file.b.com/logo.png 同样也会去校验访问控制信息信息如果资源的访问控制信息不能外表资源能够在以后站点应用,就会产生跨域的问题,浏览器会禁止资源的加载和展现。也就是说跨域问题其实是浏览器对资源应用安全策略产生的,浏览器要保障站点拜访资源是平安,非法拜访的。 浏览器是否能够应用某个资源有两个限度: 以后站点是否容许应用其余站点,域名的资源站点能够配置是否容许拜访其余域名的资源,比方在站点配置一些安全策略 # COEP 跨域嵌入策略,会阻止页面加载任何未明确授予文档许可权的跨域资源(应用CORP或CORS)。Cross-Origin-Embedder-Policy: require-corp# COOP Cross-Origin-Opener-Policy: same-origin申请的资源是否能够在以后站点应用资源是否开启了跨域资源共享策略,决定资源在哪些站点能够被应用,个别解决跨域的时候,须要在资源的响应头中配置以下选项 # 跨域资源策略Cross-Origin-Resource-Policy: same-site | same-origin | cross-origin# cross-origin 示意该资源被任何站点应用# 容许通过哪些办法拜访Access-Control-Allow-Methods: GET | POST | PUT | OPTIONS | HEAD# 容许哪些域名拜访, * 代表所有,一般来说不平安Access-Control-Allow-Origin: *什么是 SharedArrayBufferSharedArrayBuffer(以下简称 SAB) 是一个 javascript 对象,用于网站线程之间的内存数据共享,比方 worker。同样因为 WebAssembly 应用 worker 模仿了多线程,所以在这种状况下同样会应用到 SAB 做数据共享拜访。 为什么应用 SAB 会遇到跨域问题18年 spectre 曝光之前,曾经有不少网站应用了 SAB。因为 spectre 是 cpu 层面的破绽,不太可能通过cpu 降级去解决,因而过后所有浏览器间接禁用了 SAB,来避免 spectre 的旁路攻打。那么什么是 spectre 攻打,用艰深一点的话来说,就是 cpu 有一个机制,叫做预测机制,如果有一个恶意程序去查问一个没有权限的信息,操作系统会返回禁止的信息,自身来说这个逻辑当然没有问题。然而当恶意程序询问时,其实操作系统即便返回了禁止的信息,然而还是会因为预测机制会去执行正确答案的逻辑,会导致慢了一小段时间,比方几微秒,几毫米,这就被恶意程序留神到了,通过每隔一段时间一直去尝试询问,达到获取正确答案的成果。恶意程序能够通过 SAB 获取到其余页面的敏感信息。咱们都晓得 worker 在浏览器中有很大的限度,比方不能拜访 window, document 对象, SAB 是用来和主线程进行数据交换拜访的高效办法,也被大量利用,这就会导致 worker 是有方法通过 SAB 攻打获取到主线程的敏感信息。所以在 spectre 破绽曝出时,简直所有支流浏览器都默认敞开了 SAB 以应答这个破绽的攻打。 ...

December 13, 2021 · 1 min · jiezi

关于跨域:项目中遇到的跨域问题解决

带 credentials 的 CORS 申请Credentials 与 CORSCredentials(凭证) 能够是 cookies, authorization headers 或 TLS client certificates。CORS 申请默认不发送 Credentials。 如何让 CORS 申请发送 Credentials?如果要让带 credentials 的 CORS 申请胜利,前后端都须要配置。否则,即便服务器批准发送 Cookie,浏览器也不会发送。或者,服务器要求设置 Cookie,浏览器也不会解决。 Cookie 仍然遵循同源政策,只有用服务器域名设置的 Cookie 才会上传,其余域名的 Cookie 并不会上传配置后端配置:Access-Control-Allow-Credentials header;前端配置:XHR 配置 withCredentials 或 Fetch request 配置 credentials; Access-Control-Allow-Credentials 它的值是一个布尔值,示意是否容许发送 Cookie。默认状况下,Cookie 不包含在 CORS 申请之中。设为true,即示意服务器明确许可,Cookie 能够蕴含在申请中,一起发给服务器。如果服务器不要浏览器发送 Cookie,删除该字段即可。 Request.credentials credentials 是Request接口的只读属性,用于示意用户代理是否应该在跨域申请的状况下从其余域发送 cookies。有三个可选值: omit: 从不发送cookies。same-origin: 只有当URL与响应脚本同源才发送 cookies、 HTTP Basic authentication 等验证信息。include: 不论是不是跨域的申请, 总是发送申请资源域在本地的 cookies、 HTTP Basic authentication 等验证信息。XMLHttpRequest.withCredentials withCredentials是一个Boolean类型,如果是 true, 执行跨域申请时会带上 Credentials,否则不会携带。默认值是false。 ...

July 21, 2021 · 1 min · jiezi

关于跨域:深入解析各种解决跨域的方法

同源策略同源策略,它是由Netscape提出的一个驰名的安全策略。当初所有反对JavaScript 的浏览器都会应用这个策略来对脚本和申请进行校验,若不同源,则禁止应用。 同源的定义那如果判断是否同源?次要依据三个维度,域名,协定,端口三个都雷同才算同源。举个 : 网站A网站B后果http://www.zhenai.comhttp://i...不同源,域名不同http://www.zhenai.comhttp://w...不同源,域名不同http://www.zhenai.comhttps://...不同源,协定不同http://www.zhenai.comhttp://w...不同源,端口不同(默认端口80) 同源策略的作用①无奈用js读取非同源的Cookie、LocalStorage 和 IndexDB这个次要是为了避免歹意网站通过js获取用户其余网站的cookie等用户信息。 ②无奈用js获取非同源的DOM避免歹意网站通过iframe获取页面dom,从而窃取页面的信息。 ③无奈用js发送非同源的AJAX申请避免歹意的申请攻打服务器窃取数据信息。 那是不是说非同源的申请就无奈实现呢?也不是,这就引出了咱们本文次要论述的解决跨域申请问题的办法。 jsonpjsonp能实现跨域是利用了img、script和link标签本身的跨域能力。咱们晓得当img或者script中的src是一个链接的时候,浏览器会申请这个链接获取资源,那么这个链接如果是跨域的,浏览器也会申请,从而达到了跨域申请的一个性能。 用法 var script = document.createElement('script');script.src = 'http://localhost:3000/api/test.do?a=1&b=2&callback=cb';$('body').append(script);function cb(res){ // do something console.log(res)}能够看到,咱们创立一个script标签,将src改成咱们要申请的接口,并将script增加在body中,那么当浏览器解析到这个script时,会想src对应的服务器发送一个get申请,并将参数带过来。而后当浏览器接管到服务端返回的数据,就会触发参数中callbak对应的回调函数cb,从而实现整个get申请。 长处简略粗犷 毛病①只反对get申请②须要后盾配合,将返回后果包装成callback(res)的模式 防备那如果黑客植入script脚本通过jsonp的形式对服务器进行攻打,怎么办?能够通过页面设置的内容平安协定csp进行防备。 cors跨域cors 是一个W3C规范,全称是"跨域资源共享"(Cross-origin resource sharing),它容许浏览器向跨源服务器发送XMLHttpRequest申请,从而克服了 AJAX 只能同源应用的限度cors 须要浏览器和服务器同时反对,整个 CORS通信过程,都是浏览器主动实现不须要用户参加,对于开发者来说,cors的代码和失常的 ajax 没有什么差异,浏览器一旦发现跨域申请,就会增加一些附加的头信息然而,cors不反对ie10及以下版本。 简略申请和简单申请浏览器将cors申请分为简略申请和简单申请。简略申请则间接发送申请到服务器,只申请一次。而简单申请在正式申请前都会有预检申请,在浏览器中都能看到有OPTIONS申请,用于向服务器申请权限信息的,须要申请两次。 那如何辨别是简略申请还是简单申请呢? 简略申请简略申请必须要同时满足上面三个条件: 申请形式只能是:GET、POST、HEADHTTP申请头限度这几种字段:Accept、Accept-Language、Content-Language、Content-Type、Last-Event-IDContent-type只能取:application/x-www-form-urlencoded、multipart/form-data、text/plaincontent-type的类型类型形容application/json音讯主体是序列化后的 JSON 字符串application/x-www-form-urlencoded数据被编码为键值对。这是规范的编码格局multipart/form-data须要在表单中进行文件上传时,就须要应用该格局。常见的媒体格式是上传文件之时应用的text/plain数据以纯文本模式(text/json/xml/html)进行编码,其中不含任何控件或格局字符 application/json: 作用: 通知服务器申请的主题内容是json格局的字符串,服务器端会对json字符串进行解析,益处: 前端人员不须要关怀数据结构的复杂度,只有是规范的json格局就能提交胜利。application/x-www-form-urlencoded:是Jquery的Ajax申请默认形式 作用:在申请发送过程中会对数据进行序列化解决,以键值对模式?key1=value1&key2=value2的形式发送到服务器。益处: 所有浏览器都反对。简单申请不满足简略申请的条件,那么就是简单申请。简单申请会在正式申请发送之前,先发一个预检申请进行校验,校验通过后能力进行正式申请。举个浏览器当初要发送一个put的简单申请,那么在put申请发送之前,浏览器先发送一个options申请。options申请头信息: OPTIONS /cors HTTP/1.1Origin: localhost:3000Access-Control-Request-Method: PUT // 示意应用的什么HTTP申请办法Access-Control-Request-Headers: X-Custom-Header // 示意浏览器发送的自定义字段Host: localhost:3000Accept-Language: zh-CN,zh;q=0.9Connection: keep-aliveUser-Agent: Mozilla/5.0...服务器收到options申请当前,查看了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段当前,确认容许跨源申请,就能够做出回应options响应头信息 HTTP/1.1 200 OKDate: Mon, 01 Dec 2008 01:15:39 GMTServer: Apache/2.0.61 (Unix)Access-Control-Allow-Origin: http://localhost:3000 // 示意http://localhost:3000能够拜访数据Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Content-Type: text/html; charset=utf-8Content-Encoding: gzipContent-Length: 0Keep-Alive: timeout=2, max=100Connection: Keep-AliveContent-Type: text/plain当options申请通过之后收回正式的HTTP申请,假使options申请不通过,则服务器不容许此次拜访,从而抛出谬误 ...

July 20, 2021 · 1 min · jiezi

关于跨域:谈谈跨域那些事

浏览器中的HTTP申请XMLHttpRequestXHR对象用于与服务器交互。通过XMLHttpRequest能够在不刷新页面的状况下申请特定URL,获取数据。XMLHttpRequest在AJAX编程中被大量应用。 MDN文档 FetchFetch API提供了一个获取资源的接口(包含跨域申请) MDN文档 AJAXAsynchronous JavaScript And XML,是一种应用XMLHttpRequest技术构建更简单,动静的网页的编程实际。大部分的ajax其实就是对XMLHttpRequest的相干API进行封装,使其应用起来更加不便。 MDN文档 跨域跨域,顾名思义,逾越区域。大略意思为拜访的网站申请非同源资源。 因为安全性等问题,浏览器自带同源策略,所谓同源是指域名,协定,端口均雷同。当拜访的网站须要申请非同源资源时,浏览器将回绝这些非同源申请。在这种状况下,咱们须要解决浏览器跨域时拒绝请求非同源资源的限度。 当浏览器呈现跨域时,那就不可避免的引出两个要害的概念了。简略申请和非简略申请。 当跨域产生时,非简略申请会在真正向服务端发送申请前进行预检申请(OPTIONS),。 简略申请1、条件定义:若申请满足以下所有的条件,则申请可视为简略申请。 应用下列办法之一:GETHEADPOST申请首部字段不得超出以下汇合AcceptAccept-LanguageContent-LanguageConent-Type:text/plain || multipart/form-data || application/x-www-form-urlencodedDPRDownlinkSave-DataViewport-WidthWidth申请中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器申请中没有应用 ReadableStream 对象非简略申请1、条件定义:若申请满足下列任一条件时,即应首先发送预检申请(options)。 应用了上面的任一办法:PUTDELETECONNECTOPTIONSTRACEPATCH设置了额定的申请首部字段(除去以下汇合中的)AcceptAccept-LanguageContent-LanguageConent-Type:text/plain || multipart/form-data || application/x-www-form-urlencodedDPRDownlinkSave-DataViewport-WidthWidth申请中的XMLHttpRequestUpload 对象注册了任意多个事件监听器申请中应用了ReadableStream对象解决跨域的计划jsonpJSON with Padding,是JSON的一种应用模式,能够让网页从别的网域获取材料。因为同源策略,一般来说位于server1.example.com的网页无奈与不是server1.example.com的服务器沟通,而HTML的<script>元素是一个例外。利用<script>元素的这个凋谢策略,网页就能够实现跨域获取后端接口数据。 因为应用script标签的src属性,因而只反对get办法。 当应用JSONP这种计划时,前后端都要有绝对应的写法。 大抵流程就是,前端通过<script>标签的src属性向后盾接口发动申请(只反对GET申请),并且传递参数callback='response',与此同时,前端必须定义函数response(responseData),这是用来解决接口返回数据后一些操作。 当接口收到申请,返回数据格式为response(responseData)。这样,以后端承受到数据response(responseData),就刚好执行了咱们曾经定义好的response(...) 当报错如下时:起因是:后端接口没有返回callback(...) 维基百科 JSONP的原理和实现 CORSCross Origin Resource Sharing,跨域资源共享,由一系列传输的HTTP头组成,这些HTTP头决定浏览器是否阻止前端JavaScript代码获取跨域申请的响应。 MDN文档 1、Access-Control-Allow-Origin:批示申请的资源能共享给哪些域 2、Access-Control-Allow-Credentials:批示当申请的凭证标记为true时,是否响应该申请 3、Access-Control-Allow-Headers:用在对预申请的响应中,哪些HTTP办法容许拜访申请的资源 4、Access-Control-Expose-Headers:批示哪些HTTP头的名称能在响应中列出 5、Access-Control-Max-Age:批示预申请的后果能被缓存多久 6、Access-Control-Request-Headers:用于发动一个预申请,告知服务器正式申请会应用哪些HTTP头 7、Access-Control-Request-Method:用于发动一个预申请,告知服务器正式申请会应用哪一种HTTP申请办法 8、Origin:批示获取资源的申请是从什么域发动的 koa2中接口容许跨域响应,响应头部字段设置如下: ctx.set('Access-Control-Allow-Origin', '*');ctx.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, DELETE, PUT');ctx.set('Access-Control-Allow-Headers', 'X-Requested-With, User-Agent, Referer, Content-Type, Cache-Control,accesstoken'); ctx.set('Access-Control-Max-Age', '86400');ctx.set('Access-Control-Allow-Credentials', 'true');注意事项: 若增加了自定义的Header字段,必须将这个字段名增加到服务端响应头部Access-Control-Allow-Headers中,不然会报错。 我的项目踩坑:在接口响应中增加了以上容许跨域响应的头部字段,然而在开发中还报了跨域的谬误(Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request),报错的大抵意思是预检申请禁止重定向。通过排查,发现是服务端nginx做了HTTP到HTTPS的重定向设置,而我恰好是以http+ip地址的模式发动申请的,那么申请就被重定向到https了,然而,浏览器发动的预检申请是禁止重定向的,因而报错了。解决方案就是将申请地址改为https+域名的模式,这样预检申请就不会重定向了。 ...

July 1, 2021 · 1 min · jiezi

关于跨域:对象存储-COS-帮您轻松搞定跨域访问需求

背景晚期为了防止 CSRF(跨站申请伪造) 攻打,浏览器引入了 “同源策略” 机制。如果两个 URL 的协定,主机名(域名/IP),端口号统一,则视为这两个 URL “同源”,属于同一个 “域”,否则视为 “非同源”,即 “跨域”。浏览器会被动拦挡跨域的 AJAX 申请,以躲避平安危险。 “同源策略” 诚然晋升了申请的安全性,但有时咱们须要跨域申请其余域名下的资源,例如在业务域名下申请 COS 的 API 接口,或者读取 COS 存储桶中文件的内容,进行一些逻辑解决。 浏览器反对一种跨域的拜访验证的机制,即 CORS(Cross-Origin Resource Sharing 跨源资源共享)。该机制容许服务端通过返回特定的 HTTP 头部来告知浏览器是否拦挡跨域申请。 COS 反对用户在存储桶中配置 “跨域拜访 CORS” 规定,以此放行一些非法的跨域申请。 业务场景上面咱们以 博客网站开发 为例,带您理解如何在 COS 配置 CORS 规定。 用户正在开发一个博客网站,为此他将一批 markdown 文件上传到 COS,对每个 markdown 文件设置了自定义头部 x-cos-meta-keywords 示意该文章的关键词,并将文件权限设置为私有读公有写。 网站的前端 JS 脚本通过浏览器向 COS 发动 AJAX 申请,读取响应的内容以及头部信息,将内容转换为 HTML 文本,解析 x-cos-meta-keywords 中蕴含的关键词,别离挂载到页面对应的 DOM 节点下,其申请过程如下图所示: 用户网站的地址是 http://example.com,Markdown 文件的地址是 https://bucketname-1250000000.cos.ap-guangzhou.myqcloud.com/test.md 很显然,这是在 http://example.com 下对 https://bucketname-1250000000.cos.ap-guangzhou.myqcloud.com 发动申请,波及跨域。 ...

January 27, 2021 · 2 min · jiezi

关于跨域:前后端分离项目如何解决跨域问题

前后端拆散我的项目跨域问题是不可避免的。通常状况下前端由React、Vue等框架编写,通过ajax申请服务端API,传输数据用json格局。 那么为什么有跨域的问题呢?解决跨域问题有哪些形式?搞清楚这两个问题咱们须要理解一下什么是同源策略。 浏览器的同源策略 同源策略(Same origin policy)是一种平安约定,是所有支流浏览器最外围也是最根本的平安性能之一。同源策略规定:不同域的客户端脚本在没有明确受权的状况下,不能申请对方的资源。同源指的是:域名、协定、端口均雷同。 比方咱们拜访一个网站,那么这个页面申请如下地址得状况是这样的: 另外,同源策略又分如下两种状况: DOM同源策略:禁止对不同源的页面DOM进行操作,次要避免iframe的状况。比方iframe标签里放一个支付宝付款的页面,如果没有同源策略,那么钓鱼网站除了域名不同,其余的则能够和支付宝的网站截然不同。XMLHttpRequest同源策略:禁止应用XHR对象向不同源的服务器发动http申请。比方网站记录了银行的cookie,这个时候你拜访了歹意网站,黑客拿到你的cookie,再通过ajax申请之前的银行网站,便能够轻易的拿到你的银行信息。 所以,正是因为有了同源策略,大家的网络环境才绝对的平安一些。 跨域问题的解决办法 理解了同源策略,就晓得为什么会有跨域问题的产生了,都是为了平安。然而理论研发中,大家还是须要跨域去拜访资源。典型的利用场景就是前后端拆散的我的项目了。那么咱们如何去解决跨域问题呢? CORS-跨域资源共享 CORS是一种W3C规范,定义了当产生跨域问题的时候,客户端与服务端如何通信解决跨域问题。实际上就是前后端约定好定义一些自定义的http申请头,让客户端发动申请的时候可能让服务端辨认进去该申请是过还是不过。 浏览器将CORS申请分为简略申请和非简略申请: 简略申请 简略申请必须满足以下两个条件: 申请形式必须是HEAD、GET、POST三种办法之一。Http申请头必须只能是:Accept、Accept-Lanuage、Content-Lanuage、Last-Event-ID、Content-Type,其中Content-Type只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain。非简略申请 不满足简略申请条件的就是非简略申请。针对非简略申请,浏览器会发动预检申请。预检申请的意思是当浏览器查看到你的页面含有跨域申请的时候,会发送一个OPTIONS申请给对应的服务器,以检测服务器是否容许以后域名的跨域申请。如果服务端容许该域名申请,则返回204或200状态码,浏览器接管到容许申请时候再持续发送对应的GET/POST/PUT/DELETE申请。同时服务器端也会告知浏览器预检申请的缓存时长是多少,在这个工夫范畴内,浏览器不会再次发动预检申请。 原理基本上就是下面说的这些,理论业务中咱们如何通过配置来解决跨域问题呢?基本上常见的就是三种形式: nginx配置 通常咱们在nginx减少如下配置即可解决跨域问题: 用nginx这种形式是最舒服的,不须要客户端和服务端多做其余工作,对代码无入侵。 jsonp 因为script标签是不受浏览器同源策略的影响,容许跨域申请资源(咱们的每一个页面都援用了大量第三方js文件)。所以能够利用动态创建script标签,通过src属性发动跨域申请,这就是jsonp的原理。然而jsonp只反对GET申请,所以并不是一种好的形式。 服务端代码管制 能够在服务端减少对跨域申请的反对: 这种形式相当于全局过滤器,对所有申请都过滤一遍。 以上三种形式都能够肯定水平上解决跨域问题,然而nginx配置和服务端管制不能同时存在,否则会报“Access-Control-Allow-Origin Not Allow Multiple value”的谬误。集体比拟举荐nginx配置的形式,一劳永逸,不须要每个web我的项目都去编写跨域的代码。 大家在工作中有没有遇到过跨域问题呢?都是怎么解决的?欢送评论区交换探讨,独特学习~ 结尾本期就分享到这里,我是小编南风吹,专一分享好玩乏味、离奇、实用的开源我的项目及开发者工具、学习资源!心愿能与大家独特学习交换,欢送关注我的公众号【Github导航站】。 往期举荐太漂亮了!有了3款开源图标库,不必再去求设计师了 10个相见恨晚的vue.js库!用好了,事倍功半! 太及时了!13个Spring Boot练手我的项目,用好了,升职涨薪不必愁 程序员接私活必备后盾框架,不必反复造轮子,网友:太好用了! 还在从头到尾撸我的项目?这6个SpringBoot我的项目用好了,事倍功半! 「00后缩写黑话翻译器」登上GitHub热榜,中年网民终于能看懂年轻人的awsl

January 6, 2021 · 1 min · jiezi

关于跨域:react使用httpproxymiddleware跨域请求

1、http-proxy-middleware应用办法在src目录下创立setupProxy.js文件 const proxy = require("http-proxy-middleware");// app能够了解为一个express实例module.exports = function (app) { app.use( proxy(['/mock/1241', '/mock/1105'], { target: "http://10.118.71.83:3000/", // 指标服务器 changeOrigin: true // 默认false,是否须要扭转原始主机头为指标URL,是否进行代理 }), );}http-proxy-middleware会作为target的反向代理服务器,承受http://localhost:3000/mock/1241/xxx申请。 一些罕用参数阐明: // proxy 中间件的选择项var config= { target: 'http://www.example.org', // 指标服务器 host changeOrigin: true, // 默认false,是否须要扭转原始主机头为指标URL ws: true, // 是否代理websockets pathRewrite: { '^/api/old-path': '/api/new-path', // 重写申请,比方咱们源拜访的是api/old-path,那么申请会被解析为/api/new-path '^/api/remove/path': '/path' // 同上 }, router: { // 如果申请主机 == 'dev.localhost:3000', // 重写指标服务器 'http://www.example.org' 为 'http://localhost:8000' 'dev.localhost:3000' : 'http://localhost:8000' }};target:用于设置指标服务器host。changeOrigin:默认false,是否须要扭转原始主机头为指标URL。ws:设置是否代理websockets。pathRewrite:重写指标url门路。router:重写指定申请转发指标具体能够参考:https://www.cnblogs.com/zhaow... ...

December 4, 2020 · 1 min · jiezi

关于跨域:分布式电商项目八电商前台跨域问题

京淘前台我的项目搭建京淘架构图设计 JT-WEB我的项目创立创立JT-WEB服务器 增加继承/依赖/插件<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><!--我的项目坐标--><modelVersion>4.0.0</modelVersion><artifactId>jt-web</artifactId><!--应用模板打jar包 不应用模板打war包--><packaging>war</packaging><!--父级工程--><parent><artifactId>jt2007</artifactId><groupId>com.jt</groupId><version>1.0-SNAPSHOT</version></parent><!--增加依赖项--><dependencies><dependency><groupId>com.jt</groupId><artifactId>jt-common</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies><!--增加maven插件--><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>导入动态资源文件阐明:将课前材料中的文件src目录导入到jt-web中 对于主启动类阐明阐明:jt-web服务器启动时会加载数据源的自动化配置,然而web服务器没有配置数据源,所以报错.启动类上增加数据源启动. 配置工作目录 域名反向代理需要: 要求用户通过http://www.jt.com 拜访localhost:8092服务器. 改hosts文件 批改Nginx配置文件#配置前台服务器 server { listen 80; server_name www.jt.com; location / { proxy_pass http://localhost:8092; } }批改之后,重启nginx服务器 页面成果展示 谷歌浏览器禁用HTTPS键入地址:chrome://net-internals/#hsts:批改实现之后,先清空缓存之后重启浏览器 对于伪动态的阐明业务阐明问题1: 京东的商品有很多,如果都采纳动态页面的模式为用户展示数据,如果有100万的商品,那么就须要100万个商品的xxx.html页面. 问:京东是这么做的吗???实现规定:应该动静获取商品的ID号.之后查询数据库,而后调整指定的页面,将数据进行填充即可. 问题2: 为什么京东采纳.html结尾的申请展示商品呢?答案: 采纳.html结尾的页面,更加容易被搜索引擎收录,进步网站的曝光率. 搜索引擎工作原理工作原理外围: 倒排索引机制. 依据关键字检索文章的地位. 伪动态思维伪动态是绝对实在动态来讲的,通常咱们为了加强搜索引擎的敌对面,都将文章内容生成动态页面,然而有的敌人为了实时的显示一些信息。或者还想使用动静脚本解决一些问题。不能用动态的形式来展现网站内容。然而这就损失了对搜索引擎的敌对面。怎么样在两者之间找个两头办法呢,这就产生了伪动态技术。伪动态技术是指展现进去的是以html一类的动态页面模式,但其实是用ASP一类的动静脚本来解决的。 开启后缀类型匹配阐明: 因为京东商城的商品展示时通过 url:https://item.jd.com/10021377498920.html,京东的拜访是依据.html进行拦挡,之后通过restFul构造动静获取商品的ID号,之后查询数据库进行的回显.所以须要对后缀进行拦挡.有了如下的配置. package com.jt.config;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configurationpublic class MvcConfigurer implements WebMvcConfigurer{ //开启匹配后缀型配置 @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.setUseSuffixPatternMatch(true); }}URL地址小结: http://www.jt.com/index 该申请会被Controller进行拦挡.http://www.jt.com/index.html 该申请默认条件下示意获取动态资源文件.不会被拦挡.个别条件下:Controller只拦挡前缀类型的申请. 如果须要拦挡后缀类型的申请须要独自配置. 登录注册页面跳转实现通用页面跳转url1: http://www.jt.com/user/login.html 跳转页面 login.jspurl2: http://www.jt.com/user/register.html 跳转页面 register.jsp ...

November 29, 2020 · 4 min · jiezi

关于跨域:跨域问题

服务器与服务器之间发动的申请时不存在跨域的说法的,跨域是浏览器同源策略引起的,即:从某一个域申请其余域的各类资源,其中只有两个地址的域名、端口以及协定有不同的中央就会视为跨域,跨域资源无奈间接获取,这是浏览器针对javascript发动的平安限度。ng个别能够通过反向代理或者access等配置解决跨域。反向代理:是将被拜访的服务端地址反向代理至以后ng的域名地址下,来解决跨域的问题。access配置:通过对response加上容许跨域的头字段,浏览器收到response之后判断可跨域从而拜访胜利。 add_header Access-Control-Allow-Methods *; # 预检命令的缓存,如果不缓存每次会发送两次申请 add_header Access-Control-Max-Age 3600; # 带cookie申请须要加上这个字段,并设置为true add_header Access-Control-Allow-Credentials true; # 示意容许这个域跨域调用(客户端发送申请的域名和端口) # $http_origin动静获取申请客户端申请的域 不必*的起因是带cookie的申请不反对*号 add_header Access-Control-Allow-Origin $http_origin; # 示意申请头的字段 动静获取 add_header Access-Control-Allow-Headers $http_access_control_request_headers;

November 3, 2020 · 1 min · jiezi

nginx-配置跨域失效修复

nginx 配置跨域不生效 如下配置server { listen 80; server_name localhost; # 接口转发 location /api/ { # 允许请求地址跨域 * 做为通配符 add_header 'Access-Control-Allow-Origin' '*'; # 设置请求方法跨域 add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE'; # 设置是否允许 cookie 传输 add_header 'Access-Control-Allow-Credentials' 'true'; # 设置请求头 这里为什么不设置通配符 * 因为不支持 add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,X-Data-Type,X-Requested-With,X-Data-Type,X-Auth-Token'; # 设置反向代理 proxy_pass 127.0.0.1:8081/; } }网上的 nginx 跨域配置主要是以上版本,然而很多都是抄一抄,并没有真的去实践,故写了下文章提醒下有需要的人,不要盲目抄,学会分析。nginx 修改如下配置后生效server { listen 80; server_name localhost; # 接口转发 location /api/ { # 允许请求地址跨域 * 做为通配符 add_header 'Access-Control-Allow-Origin' '*'; # 设置请求方法跨域 add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE'; # 设置是否允许 cookie 传输 add_header 'Access-Control-Allow-Credentials' 'true'; # 设置请求头 这里为什么不设置通配符 * 因为不支持 add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,X-Data-Type,X-Requested-With,X-Data-Type,X-Auth-Token'; # 设置 options 请求处理 if ( $request_method = 'OPTIONS' ) { return 200; } # 设置反向代理 proxy_pass 127.0.0.1:8081/; } }两者代码区别 主要就是下面这行代码 ...

October 18, 2019 · 1 min · jiezi

vuecli3配置与跨域处理

安装vue-cli3环境准备1.如果您已安装vue-cli2,请先删除当前脚手架,否则无法成功安装vue-cli3。 npm uninstall vue-cli -g2. 检查node.js版本。vue-cli3需要node版本大于8.9。在cmd中输入node -v查看版本。如果版本过低,请先去node官网中下载高版本。 安装脚手架vue-cli3的包名有vue-cli改为@vue/cli。 使用npm全局安装vue-cli3。 npm install @vue/cli -g然后使用vue -V 使用vue-cli3创建项目vue-cli3创建项目的方式和2.x也有一些区别。首先创建项目时给我们更多可配置的选项,在选择手动选择特性时,可以根据提示选择是否安装vue-router、vuex等。其次其目录结构也可2.x有区分,下文会详细讲。然后我们可以把本次安装配置保存为以后可以复用的preset,在以后创建项目时更快速。 1.输入vue create vue_cli3_test,上下键可以选择默认安装或者手动安装。这次我选择手动安装。 2.点击回车键,进入配置界面。将需要安装的配置前面勾选为*。空格键可以选中和取消选中。可以根据个人需求进行配置。安装成功后根据提示进入目录,并运行项目。 cd vue_cli3_testyarn serve 3.vue-cli3提供了图形界面方式进行创建项目, 可以在网页上直接进行项目的配置。vue ui 4.打开目录时发现配置文件都在了,大家不要慌张。可以在package.json文件的同级目录下创建vue.config.js文件。文件的格式应该为 // vue.config.jsmodule.exports = { // 选项...}下面为我的基础配置: module.exports = { outputDir: 'dist', //build输出目录 assetsDir: 'assets', //静态资源目录(js, css, img) lintOnSave: false, //是否开启eslint devServer: { open: true, //是否自动弹出浏览器页面 host: "localhost", port: '8081', https: false, //是否使用https协议 hotOnly: false, //是否开启热更新 proxy: null, }}官网给出了丰富而全面的配置,更多配置请参考https://cli.vuejs.org/zh/conf...。 ...

August 17, 2019 · 1 min · jiezi

从前端角度用正向反向代理解决开发时的跨域问题

前言这篇文章的初衷是结合我看到其他前辈们的文章所总结的一些心得,可能不会很全面,不过大概的知识流程应该还是能传达给大家的。 内容大概浏览器输入url的时候,到回来显示整个大致流程正向代理,反向代理前端处理跨域浏览器输入url之后,到页面显示回来的大致过程(前端角度)举例:比如我准备在浏览器输入https://baidu.com 过程分析:第一步:获取IP地址 (1)在浏览器输入地址之后,注意,咱们这个baidu是域名,不是IP地址; (2)这个时候浏览器会去找这个域名对应的是哪个IP地址,一般来说,会先找我们电脑本地硬盘的 hosts 文件,看一下有没有相关规则; (3)如果本地hosts文件没有找到IP地址,那这个时候就去会去DNS服务器找,并返回IP地址; 第二步:发请求,建立TCP连接(三次握手)(1)这里的细节我不过多说,我们只需要知道,上一步获取到IP地址之后,客户端开始向这个IP地址发送请求前,会先进行TCP连接; 三次握手是指这个连接的过程中:1)客户端先发送一次报文给服务器;2)服务端收到,再发一个给客户端;3)客户端接受到,再发一次报文,同时也把这次的HTTP请求一起发过去 (2)经过握手之后,TCP连接成功,服务端这时也接受到了请求(在最后一次握手),接着处理之后把响应发回给客户端的同时,服务端会先单方面关闭TCP连接; (3)客户端接受到请求数据,也把TCP连接关闭,这时才算完成一次请求; 小结论:从这里我们就可以知道,为什么要说前端减少HTTP请求会对性能有优化作用,就是因为你每一个请求它都要跑上面的过程,握手耗时 第三步:浏览器拿到html文件,开始渲染,并且发送请求获取嵌入在 HTML 中的资源(如CSS、JS、图片、音频、视频等)(这里后面再补充) 代理1、在讲之前先说一下最终的结论(个人理解,可能有误) 正向代理:知道真正的请求地址,在向这个地址发送请求的时候,被代理服务器拦截,然后帮你去请求这个地址,最后把请求结果从真实服务器拿回来,再返回给你;反向代理:不知道真正的请求地址,知道一个假的,在你请求这个假地址(这个假地址就是在代理配置的,当然也把正确的地址配置进去,通常是在服务端配置这个代理服务器,所以说这个真实地址对客户端是透明的)的时候,被代理服务器拦截,然后步骤跟上面一样;2、讲解(1)正向代理说明:正向代理(forward)是一个位于客户端【用户A】和原始服务器(origin server)【服务器B】之间的服务器【代理服务器Z】,为了从原始服务器取得内容,用户A向代理服务器Z发送一个请求并指定目标(服务器B),然后代理服务器Z向服务器B转交请求并将获得的内容返回给客户端。客户端必须要进行一些特别的设置才能使用正向代理。 通俗大白话:正向代理需要在客户端设置,在这个过程中,真正的服务器B并不知道到底是哪个客户端发起的请求,因为它所有的请求都是来自代理服务器Z,所以说,在正向代理中,我们会说此时客户端是透明的。 正向代理用途:1)科学上网;2)对客户端访问授权,上网进行认证3)可以做缓存,加速访问资源 (2)反向代理说明:反向代理正好与正向代理相反,对于客户端而言代理服务器就像是原始服务器,并且客户端不需要进行任何特别的设置。客户端向反向代理的命名空间(name-space)中的内容发送普通请求,接着反向代理将判断向何处(原始服务器)转交请求,并将获得的内容返回给客户端。 通俗大白话:反向代理不需要在客户端设置,在这个过程,客户端只需要请求这个代理服务器,这个代理服务器会自动根据相关设置去请求对应的真正的服务器,所以说,在反向代理中,我们会说此时服务端是透明的。 反向代理用途:1)负载均衡,减轻服务器压力(目前只知道这个); (3)小结论是不是觉得有点奇怪?两个都是请求代理服务器,都是由代理服务器去请求真正的服务器?到底有什么不同?请看我最开始上面说的结论。 (4)图解借用知乎两张图来表达:https://www.zhihu.com/questio... 跨域(重点来了)我前面说了这么多,什么敲入地址,代理啊,都是为了讲一下在我们平时的开发中,怎么解决跨域这个问题。 (1)概念跨域是由浏览器同源策略引起的,是指页面请求的接口地址,必须与页面url地址处于同域上(即域名,端口,协议相同)。这是为了防止某域名下的接口被其他域名下的网页非法调用,是浏览器对JavaScript施加的安全限制。 注意:通过后台调接口是不会有跨域问题的,而是会出现这个接口设置了不让其他服务器调用的其他问题 例子:从http://www.lizi.com/home/index.html向以下地址发送请求 1. http://www.lizi.com/home/detail.html 成功,路径不同 2. http://www.lizi.com/description/detail.html 成功,路径不同 3. https://www.lizi.com/home/list.html 失败,协议不同(http 和 https) 4. http://www.lizi.com:8848/home/manange.html 失败, 端口不同(默认80 和 8848) 5. http://mobile.lizi.com/home/secret.html 失败,域名不同(www 和 mobile)(2)怎么解决呢虽然浏览器有限制,但是HTML中有两个属性是不受限制的,那就是src和href属性 <img src="http://www.lizi.com/xxx.jpeg"></script><link rel="stylesheet" href="http://www.lizi.com/css/reset.css"> <script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.min.js"></script><link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">是不是很熟悉?我们平时开发中上面代码肯定经常用。根据这个特性,前端大佬们想到了一个解决跨域的方式---JSONP2.1)JSONP先说结论:这个方法是前后端要互相配合,比如约定那个函数的名字叫啥之类 //这个是请求页面//里面有一个script标签,它的src是一个请求地址,在地址后面拼接了一个参数callback,值为sendStudent//页面也写了一个sendStudent的函数,跟传过去的callback值是对应的//当这个请求成功返回来之后,咱们页面这个sendStudent就会自动触发//当然触发的时机,是服务端那边执行了 sendStudent('数据数据') 这段代码,这个写法就是咱们JS触发函数的写法,只是现在把这段代码放在了服务端<!doctype html><html lang="en"><head> <meta charset="UTF-8"> <title>JSONP</title> <script src="http://www.baidu.com/api/student?callback=sendStudent"></script></head><body><script> function sendStudent(res){ console.log('这里就是服务端返回来的数据',res); }</script></body></html>//假装这里是后台代码xxxxxxxxxsendStudent({user:'我是数据'})2.2)CORS先说结论:这个只需要后端设置就行了,前端不用 ...

July 11, 2019 · 1 min · jiezi

对前后端通信的基本了解如何通信跨域

1、什么是浏览器的同源政策限制?端口,域名,协议 ,只要一个不一样就跨域2、前后端如何通信?常见通信的几种方式 Ajax : 短连接Websocket : 长连接,双向的。CORS fetch()Form表单(最原始的)Ajax是如何通信的 基本通信原理:浏览器可以发出HTTP请求与接收HTTP响应,实现在页面不刷新的情况下和服务端进行数据交互。实现过程:1) 创建XMLHttpRequest对象(异步调用对象)var xhr = new XMLHttpRequest();2) 创建新的Http请求(方法、URL、是否异步)xhr.open(‘get’,’example.php’,false);3) 设置响应HTTP请求状态变化的函数。onreadystatechange事件中readyState属性等于4。响应的HTTP状态为status==200(OK)或者304(Not Modified)。4) 发送http请求xhr.send(data);5) 获取异步调用返回的数据优点:提高用户体验,较少网络数据的传输量Fome表单是如何通信 基本通信原理:通过form表单以post/get方式提交数据。实现过程:当你点击submit按钮时,浏览器会默认把你在input里面输入的数据,以post或get的方式提交到form表单中的action这个地址。相当于你提交表单时,就会向服务器发送一个请求,然后服务器会接受并处理提交过来的form表单,最后返回一个新的网页。缺点:1、单项提交,页面会发生跳转或刷新,导致用户体验不好3、浪费宽带。改用ajax。了解Websocket 建立在TCP协议之上,与HTTP协议有着良好的兼容性3、跨域通信有几种?引JSONPHash(url#后面的,改变页面不刷新)postMessage(H5中新增的)WebSocketCORS介绍以下最常用的JSONP 1.JSONP原理利用<script元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。2.JSONP和AJAX对比JSONP和AJAX相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求)3.JSONP优缺点JSONP优点是兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get方法具有局限性4、GET与post区别?POST与GET的区别1、GET请求会被浏览器主动缓存,而POST不会2、GET请求参数会被完整保留在浏览器历史记录里,而POST中参数不会被保留3、GET请求在URL中传送的参数是有长度限制的,而POST没有限制4、GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息本质上都建立在TCP链接上

July 3, 2019 · 1 min · jiezi

vuecli30-axios-跨域多个代理

在使用vue-cli3.0 结合 axios 请求后台多个server,遇到跨域问题,进行以下几个步骤的改动,就可解决1、vue.config.js devServer: { open: true, port: 8081, proxy: { '/api': { target: process.env.TARGET1, changeOrigin: true, pathRewrite: { '^/api': '', }, logLevel: 'debug', }, '/ips': { target: process.env.TARGET2, changeOrigin: true, pathRewrite: { '^/ips': '', }, logLevel: 'debug', }, }, },2、.env TARGET1= "http://10.50.60.100:8088"TARGET2= "http://10.50.60.100:50050"3、api export function test1(data) { return request({ url: 'api/test1', method: 'post', data, });}export function test2(data) { return request({ url: 'ips/test2', method: 'post', data, });}

June 26, 2019 · 1 min · jiezi

解决前端开发环境中的的跨域问题

一、为什么会有跨越问题是客户端浏览器同源策略导致的,就是浏览器不允许不同源的站点相互访问。试想一下要是没有这个,那站点里的安全信息如cookie,账号/密码等是不是很容易被其它站点获取。二、解决思路知道是客户端浏览器为了安全使用同源策略导致的,而服务端是没有这个限制的,那我们就只能通过服务端进行跨域了。不管是jsop,core,还是代理的方式,都是需要服务配合的。哈哈,这也是为啥后端和生产环境下比较少听说跨域的问题,所以这里介绍开发环境中的几种方法。三、解决方案1.完全交予后端解决,配值请求头信息(core),前端什么都不用做,如express.js中配置如下,其它后端语言同理 app.all('*', function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "X-Requested-With"); res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS"); res.header("X-Powered-By",'3.2.1') res.header("Content-Type", "application/json;charset=utf-8"); next();});2.使用nginx反向代理,在配置文件nginx.conf中找到server{}对象,更改项目地址root和配置代理地址proxy_pass,这个方法适合前端静态文件使用: location / { root D:/browseClient/dist; #自己的前端项目地址 index index.html index.htm;}#解决跨域location /api { # 自定义nginx接口前缀 proxy_pass http://127.0.0.1:3000; # 后台api接口地址 proxy_redirect default; #设置主机头和客户端真实地址,以便服务器获取客户端真实IP proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }3.如果使用vue-cli搭建的项目,可以直接使用proxyTable模块,项目框架已经集成,找到配置文件在/confif/index.j,如下配置 //代理配置表,在这里可以配置特定的请求代理到对应的API接口proxyTable:{ "/adv":{ target:"http://127.0.0.1:3000",//需要访问的服务器地址 changeOrigin:true,//true为开启代理 pathRewrite:{ '^/adv': '/'//路径的替换规则 } }, "/user":{ target:"http://127.0.0.1:3000",//访问的服务器地址 changeOrigin:true,//true为开启代理 pathRewrite:{ '^/user': '/'//路径的替换规则 } }}显然,大部分情况我们不可能为每个api接口都在这加一个规则,所以更好的配置是: proxyTable: { '**': { target: 'http://127.0.0.1:3000', changeOrigin: true, //允许跨域 }}或者 ...

June 25, 2019 · 1 min · jiezi

axios跨域请求默认不携带cookie

最近开发一款前后端分离的应用,后端接口全部完成,正在对接的时候发现死活登录不上。前端是本地server,跑在localhost上,后端接口部署在测试服务器上。后端已经允许了跨域,接口也能跑通,但是就是登录不上。而且接口的表现十分奇怪,request headers里显示一个感叹号+Provisional headers are shown。如下图:![图片描述][1]由于登录不上,怀疑是否是cookie未携带,但是无论如何操作,chrome面板总是只显示这4个header。尝试用QQ浏览器打开查看header,感叹号+Provisional headers are shown没有了,显示了实际的request headers。如下图:![图片描述][2]发现确实没有携带cookie。查阅axios文档,配置项有一个withCredentials,表示跨域请求时是否需要使用凭证,默认值为false。即axios 在发起跨域时默认不携带cookie,将withCredentials设置为true即可正常携带cookie。

June 18, 2019 · 1 min · jiezi

vue-proxyTable-配置代理解决跨域

vue + webpack 构建的项目解决浏览器跨域问题在 config/index.js 中配置 proxyTable 代理,如下图所示: proxyTable: { // 配置代理 '/api': { // 匹配所有以 '/api' 开头的请求路径 target: 'http://localhost:4000', // 代理目标的基础路径 changeOrigin: true, // 支持跨域 pathRewrite: { // 重写路径: 去掉路径中开头的'/api' '^/api': '' } }},

June 11, 2019 · 1 min · jiezi

vuecli使用webpack代理解决跨域问题

可以在vue-cli创建的项目目录config/index.js,找到dev.proxyTable属性,在上面写代理域名即可

May 15, 2019 · 1 min · jiezi

一文读懂JSONP原理

跨域为什么会出现跨域? 因为浏览器有同源策略的限制,同源策略是浏览器最核心最基础的安全策略。端口,协议,域名,有一者不同就会出现跨域的问题。解决跨域的方式JSONPCORSJSONP怎样解决跨域?所谓的JSONP解决跨域问题就是前端在合适的时期动态添加一个<script>标签,请求后端给的接口带上一个回调函数。 因为<script>标签不受浏览器同源策略的限制。 手摸手带你解开JSONP的原理实现JSONP跨域和JSONP原理的几步 **前端**前端先定义一个回调函数<script type="text/javascript" charset="utf-8"> function callback(data){ console.log(data) }</script>2.在合适的阶段通过<script>标签请求后端给出的地址带上callback回调参数 <script src="http://192.168.1.107:3000?callback=callback" type="text/javascript" charset="utf-8"></script>后端 开启一个接口服务var express = require('express');var app = express();app.get('/',function(req,res,next){ res.end("ok")})app.listen(3000,function(){ console.log('JSONP') })2.等前端请求接口的时候,获取请求的参数的回调 app.get('/',function(req,res,next){ var callback = req.query.callback; })3.最后后端把所需的数据放到获取到的回调函数参数内,返回给前端(返回的是字符串),浏览器会把它解析为js执行 app.get('/',function(req,res,next){ var callback = req.query.callback; //模拟所需的数据 var data = { err_ok:0, message:"请求成功", data:{ name:"july", age:21 } } res.end(`${callback}(${JSON.stringify(data)})`) })最终后端代码 var express = require('express');var app = express();app.get('/',function(req,res,next){ var callback = req.query.callback; //模拟所需的数据 var data = { err_ok:0, message:"请求成功", data:{ name:"july", age:21 } } res.end(`${callback}(${JSON.stringify(data)})`) })app.listen(3000,function(){ console.log('JSONP') })最终前端的代码 ...

May 7, 2019 · 1 min · jiezi

Provisional-headers-are-shown错误

使用chrome谷歌浏览器前后端接口调试的时候遇到了这个问题:network Provisional headers are shownconsole Cross-Origin Read Blocking (CORB) blocked cross-origin response http://xxx:180/test?id=1035 with MIME type application/json. See https://www.chromestatus.com/feature/5629709824032768 for more details.如果使用其它浏览器或者postman等测试工具测试的话又可以调用。 错误原因其实是跨域的问题,后端需要设置允许跨域就可以了。 我的项目是spring boot项目,配置跨域如下: @Configurationpublic class CorsConfig { private CorsConfiguration buildConfig() { org.springframework.web.cors.CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOrigin("http://127.0.0.1:800"); // 1这就是前端调用放的origin,可以在network中查看 corsConfiguration.addAllowedHeader("*"); // 2允许任何头 corsConfiguration.addAllowedMethod("*"); // 3允许任何方法(post、get等) return corsConfiguration; } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", buildConfig()); // 4 return new CorsFilter(source); }}

May 7, 2019 · 1 min · jiezi

新手学跨域之代理转发

jsonpiframe● document.domain● window.name● location.hash● navigator (IE6 bug)● postMessage CORS● XMLHttpRequest (modern browser)● XDomainRequest (IE8+) 图像Ping (略)flash (略)代理/转发● http-server● webpack-dev-server● nginx 以前写的这个系列的时候,是看了一些资料之后通过实操,从一个前端初学者的角度记录验证过程。 但是,在近年来的工作中发现用到的场景并不多,实际工作中大部分遇到的跨域问题还是在调用接口时。 虽然有了CORS方案,但是CORS实际开发中并不友好。首先它不支持低版本浏览器,当然现在前端开发基本可以忽略了,更主要的是CORS是需要服务器配置的,而这个一般是后台同事负责。第一次接口请求报错了,提醒他们要加上允许请求的域,后来接口新增了新的请求方法或请求头字段,又要提醒他们,遇到有经验又有耐心的后台开发小哥哥还好,不然就等着吧。 我们回到本质,为什么会有跨域问题,原因是浏览器的同源策略。所以如果调用的接口与页面同域或调用接口的不是浏览器就没有跨域问题了。现在把两者结合起来就可以实现跨域请求,即前端把请求发送给页面服务器,页面服务器请求接口服务器,拿到数据后再返回给前端,这里页面静态服务器充当了代理/转发的角色。 现在我们具体看看怎么实现代理/转发,假设要请求的接口为https://api.yyy.com/data.php http-server文档:https://www.npmjs.com/package... 现在前端开发一般会在本地使用Node开启静态服务器,可以用http-server这个包创建一个简单的静态服务器,加上选项-P或--proxy即可实现接口代理/转发 http-server -P https://api.yyy.com现在为当前目录开启了一个静态服务器,前端直接请求即可拿到接口数据 fetch('data.php').then(console.log)请求当前data.php相当于请求https://api.yyy.com/data.php,并且没有跨域问题。 http-server优点是简单快捷,缺点是不够灵活,且不能自动监听代码改动并刷新页面。BrowserSync可能是更好的选择。 webpack-dev-server文档:https://webpack.js.org/config... http-server适合本地写些简单页面、小demo,而我们开发复杂些的项目,比如基于Vue.js、React等来构建项目一般会用到webpack,并且在开发阶段使用webpack-dev-server,这里只介绍它的代理部分,它的代理实现是基于http-proxy-middleware封装。在webpack.config.js里配置 devServer: { // ... proxy: { '/api': { // 如果是匹配所有接口请求可以用'**' target: 'https://api.vczhan.com', changeOrigin: true, pathRewrite: {'^/api' : ''} // 本地请求中加上了api以区别不同接口请求,但真正的接口是没有的,所以这里对它重写 } }}前端请求 fetch('api/data.php').then(console.log)这里请求api/data.php会转发到https://api.yyy.com/data.php。 nginx前端代码打包后传到服务器上,需要一个web服务器,这样才能访问页面,这里选择nginx,它既轻便又强大,可配置的规则很多,这里只展示最简单的代理配置 server { listen 80; server_name xxx.com location /api/ { proxy_pass https://api.yyy.com/; # 转发到api.yyy.com # proxy_pass https://api.yyy.com; # 转发到api.yyy.com/api/ }}同样的请求http://xxx.com/api/data.php会转发到https://api.yyy.com/data.php。 ...

May 1, 2019 · 1 min · jiezi

主流的跨域解决方案

跨域的方式有很多种,但是主流的方式主要是以下两种: CORS 跨域资源共享(Cross Origin Resource Sharing) 这种方式对前端来说和平时发请求写法上没有任何区别,工作量基本都在后端这里。每一次请求浏览器必须先以 OPTIONS 请求方式发送一个预请求,从而获知服务器端对跨源请求所支持 HTTP 方法。在确认服务器允许该跨源请求的情况下,以实际的 HTTP 请求方法发送那个真正的请求。推荐的原因是只要第一次配好了,之后不管有多少接口和项目复用就可以了,一劳永逸的解决了跨域问题,而且不管是开发环境还是测试环境都能方便的使用。 webpack 的 proxy代理 在dev 开发模式下可以下使用webpack的proxy代理。具体使用方法可以看下具体文档。但这种方法在生产环境中是不适用的,在生产环境中需要使用Nginx反向代理。不管是 proxy和nginx的原理都是一样的,通过搭建一个中转服务器来转发请求规避跨域的问题。 原文链接:https://juejin.im/post/595b4d...

April 30, 2019 · 1 min · jiezi

跨域再也不心虚了

文章列出解决方案以及对应的demo, 拒绝说概念,不在稀里糊涂。什么情况出现跨域?协议不同域名不同端口不同跨域解决方案1.同一个主域下不同子域之间的跨域请求 - document.domain+iframe同一个 origin 下,父页面可以通过 iframe.contentWindow 直接访问 iframe 的全局变量、DOM 树等,iframe 可以也通过 parent/top 对父页面做同样的事情。 domain.html <body> <iframe id="ifr" src="http://b.tblog.com:3004/domain2.html"></iframe> <script> document.domain = 'tblog.com'; function aa(str) { console.log(str); } window.onload = function () { document.querySelector('#ifr').contentWindow.bb('aaa'); } </script>domain2.html <body> 2222222222 <script> document.domain = 'tblog.com'; function bb(str) { console.log(str); } parent.aa('bbb'); </script></body>完整demo 2. 完全不同源 - postMessagehtml5新增API, 支持IE8+。 otherWindow.postMessage(message, targetOrigin, [transfer]);otherWindow 其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames。message 将要发送到其他 window的数据targetOrigin 通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串""(表示无限制)或者一个URI。如果你明确的知道消息应该发送到哪个窗口,那么请始终提供一个有确切值的targetOrigin,而不是。不提供确切的目标将导致数据泄露到任何对数据感兴趣的恶意站点。transfer 可选 是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。传递过来的message的属性有: data 从其他 window 中传递过来的对象。origin 调用 postMessage 时消息发送方窗口的 origin . 这个字符串由 协议、“://“、域名、“ : 端口号”拼接而成source 对发送消息的窗口对象的引用; 您可以使用此来在具有不同origin的两个窗口之间建立双向通信下面index.html和index2.html通信 index.html ...

April 27, 2019 · 5 min · jiezi

nginx 之 proxy_pass 接口转发的规则

今天上了一个多页应用,发现接口转发后是 401。 最后发现是接口转发出错了。地址里多拼了一个 路径。 以前我一直以为location 字段会替换掉 proxy_pass 里面配置的 url 地址。 今天了解了一下 发现还是有不少细节的。// node js 文件,打印请求路径。方便查看后端真实接受到的请求地址const http = require(‘http’);http.createServer((req, res) => { console.log(req.url); res.end(您的 请求 地址是${req.url});}).listen(3000);proxy_pass 如何转发,首先看 proxy_pass 的url 配置。proxy_pass 只是HOSTproxy_pass 只是HOST,不包含任何路径,比如* http://host - √* https://host - √* http://host:port - √* https://host:port - √* http://host/ - x* http://host:port/ - x这种情况下,会把匹配到的所有路径直接穿透转发。比如以下的配置 location /api/ { proxy_pass http://127.0.0.1:3000; }访问 http://127.0.0.1:80/api/cc, 后端结果为 您的 请求 地址是/api/ccproxy_pass 包含路径这里的路径哪怕只是一个 / 也是存在的,如:http://host - xhttps//host/ - √http://host:port- xhttps://host:port/ - √http://host/api - √http://host/api/ - √这种情况下,url 里面会去掉 location 匹配的字符串,拼接到 proxy_pass 再进行转发。 location /api/ { proxy_pass http://127.0.0.1:3000/; }访问 http://127.0.0.1:81/api/cc, 后端结果为 您的 请求 地址是/cc重写代理链接 - url rewrite使用 rewrite 指令并且生效后,proxy_pass url 链接中的路径会被忽略,如:server { listen 83; location / { rewrite ^/api/(.) /fixpath=$1 break; proxy_pass http://127.0.0.1:3000/node/; } location ^/api/ { rewrite ^/api/(.) /fixpath=$1 break; proxy_pass http://127.0.0.1:3000/node/; } }访问 http://127.0.0.1:83/bb/cc 得到 您的 请求 地址是/node/bb/cc(匹配上 / 了,没有匹配 rewrite)访问 http://127.0.0.1:83/api/cc 得到 您的 请求 地址是/fixpath=cc (我们写的 proxy_pass http://127.0.0.1:3000/node/ 里面的 node路径丢失了 )知道了这几点,碰到转发接口也有一点底气啦~更多在github上看到的这本小书 ⬇️(参考资料)[https://xuexb.github.io/learn…]我的 nginx 配置events {}http { # proxy_pass url 只是 host # 这时候 location 匹配的完整路径将直接透传给 url ,如: server { listen 80; location / { proxy_pass http://127.0.0.1:3000; } location /api/ { proxy_pass http://127.0.0.1:3000; } } # url 包含路径 # 当 proxy_pass url 的 url 包含路径时,匹配时会根据 location 的匹配后的链接透传给 url ,注意匹配后就是这样: server { listen 81; location / { proxy_pass http://127.0.0.1:3000/; } location /api/ { proxy_pass http://127.0.0.1:3000/; } location /bpi/ { proxy_pass http://127.0.0.1:3000/v1; } location /cpi { proxy_pass http://127.0.0.1:3000/v1; } } # 当 location 以正则形式匹配时,proxy_pass 就不能以 / 结束了,也就是不能包含路径了, 会提示配置错误,比如错误的: server { listen 82; location / { proxy_pass http://127.0.0.1:3000/; } # nginx: [emerg] “proxy_pass” cannot have URI part in location given by regular expression, or inside named location, or inside “if” statement, or inside “limit_except” block in /test.conf:47 # location ~* ^/api/ { # proxy_pass http://127.0.0.1:3000/; # } # nginx: [emerg] “proxy_pass” cannot have URI part in location given by regular expression, or inside named location, or inside “if” statement, or inside “limit_except” block in /Users/tangdaoyuan/code/anheng/jz-bingjiang/test.conf:52 # location ~* ^/api/ { # proxy_pass http://127.0.0.1:3000/b1; # } } # 使用 rewrite 指令并且生效后,proxy_pass url 链接中的路径会被忽略,如: server { listen 83; location / { proxy_pass http://127.0.0.1:3000/node/; } location ^/api/ { rewrite ^/api/(.*) /fixpath=$1 break; proxy_pass http://127.0.0.1:3000/node/; } }}测试流程 : node 运行 服务, 启动Nginx 转发 , 再用postman 发送请求。 ...

April 20, 2019 · 2 min · jiezi

跨域方案总结

平时在开发中总是会遇到各种跨域问题,一直没有很好地了解其中的原理,以及其各种实现方案。今天在这好好总结一下。本文完整的源代码请猛戳github博客,建议大家动手敲敲代码。1、什么是跨域?为什么会有跨域?一般来说,当一个请求url的协议、域名、端口三者之间任意一个与当前页面地址不同即为跨域。之所以会跨域,是因为受到了同源策略的限制,同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。为什么会有同源策略呢?同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。设想这样一种情况:A网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取A网站的 Cookie,会发生什么?很显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。由此可见,“同源政策"是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。同源策略限制内容有:Cookie、LocalStorage、IndexedDB 等存储性内容DOM 节点AJAX 请求发送后,结果被浏览器拦截了下面为允许跨域资源嵌入的示例,即一些不受同源策略影响的标签示例:<script src=”…"></script>标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。<link rel=“stylesheet” href="…">标签嵌入CSS。由于CSS的松散的语法规则,CSS的跨域需要一个设置正确的Content-Type消息头。不同浏览器有不同的限制: IE, Firefox, Chrome, Safari 和 Opera。<img>嵌入图片。支持的图片格式包括PNG,JPEG,GIF,BMP,SVG<video> 和 <audio>嵌入多媒体资源。<object>, <embed> 和 <applet>的插件。@font-face引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。<frame>和<iframe>载入的任何资源。站点可以使用X-Frame-Options消息头来阻止这种形式的跨域交互。常见的跨域场景URL 说明 是否允许通信http://www.domain.com/a.jshttp://www.domain.com/b.js 同一域名,不同文件或路径 允许http://www.domain.com/lab/c.jshttp://www.domain.com:8000/a.jshttp://www.domain.com/b.js 同一域名,不同端口 不允许 http://www.domain.com/a.jshttps://www.domain.com/b.js 同一域名,不同协议 不允许 http://www.domain.com/a.jshttp://192.168.4.12/b.js 域名和域名对应相同ip 不允许 http://www.domain.com/a.jshttp://x.domain.com/b.js 主域相同,子域不同 不允许http://domain.com/c.js http://www.domain1.com/a.jshttp://www.domain2.com/b.js 不同域名 不允许注意:关于跨域,有两个误区:1、动态请求就会有跨域的问题(错)。跨域只存在于浏览器端,不存在于安卓/ios/Node.js/python/ java等其它环境2、跨域就是请求发不出去了(错)。跨域请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了2、跨域的解决方案2.1、jsonpjsonp的跨域原理是利用script标签不受跨域限制而形成的一种方案。下面我们来简单看一下代码实现<!– index.html 文件 –><!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <title></title></head><body><script> var script = document.createElement(‘script’); script.type = ’text/javascript’; // 传参并指定回调执行函数为onBack script.src = ‘http://127.0.0.1:3000/login?user=admin&callback=onBack’; document.head.appendChild(script); // 回调执行函数 function onBack(res) { alert(JSON.stringify(res)); }</script></body></html>nodevar qs = require(‘querystring’);var http = require(‘http’);var server = http.createServer();server.on(‘request’, function(req, res) { console.log(req); 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(‘3000’);console.log(‘Server is running at port 3000…’);我们可以看到返回的结果:优点:兼容性好(兼容低版本IE)缺点:1.JSONP只支持GET请求; 2.XMLHttpRequest相对于JSONP有着更好的错误处理机制2.2、postMessagepostMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一。语法:otherWindow.postMessage(message, targetOrigin, [transfer]);otherWindow:指目标窗口,也就是给哪个window发消息,是 window.frames 属性的成员或者由 window.open 方法创建的窗口;message 属性是要发送的消息,类型为 String、Object (IE8、9 不支持);data 属性为 window.postMessage 的第一个参数;origin 属性表示调用window.postMessage() 方法时调用页面的当前状态;source 属性记录调用 window.postMessage() 方法的窗口信息;targetOrigin:属性来指定哪些窗口能接收到消息事件,其值可以是字符串""(表示无限制)或者一个URI。transfer:是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。看一下简单的demo<!– index.html 文件 –><!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <title></title></head><body><h1>AAAAAAAAAAAA</h1><iframe src=“http://localhost:4000/b.html” id=“frame” onload=“load()"></iframe><script> function load(params){ let iframe = document.getElementById(‘frame’); iframe.onload = function() { const data = { name: ‘aym’ }; //获取iframe中的窗口,给iframe里嵌入的window发消息 iframe.contentWindow.postMessage(JSON.stringify(data), ‘http://localhost:4000’); }; // 接收b.html回过来的消息 window.onmessage = function(e){ console.log(e.data) } }</script></body></html><!– b.html 文件 –><!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <title></title></head><body><h1>BBBBBBBBB</h1><script> window.addEventListener(‘message’, function(e) { console.log(‘data from domain1 —> ’ + e.data); let data = JSON.parse(e.data); if (data) { data.number = 16; // 处理后再发回domain1 window.parent.postMessage(JSON.stringify(data), ‘http://127.0.0.1:8080’); } }, false);</script></body></html>2.3、websocketWebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。<!– index.html 文件 –><!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <title></title></head><body><div>user input:<input type=“text”></div><script src=“https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.dev.js"></script><script> var socket = io(‘http://127.0.0.1:8080’); // 连接成功处理 socket.on(‘connect’, function() { // 监听服务端消息 socket.on(‘message’, function(msg) { console.log(‘data from server: —> ’ + msg); }); // 监听服务端关闭 socket.on(‘disconnect’, function() { console.log(‘Server socket has closed.’); }); }); document.getElementsByTagName(‘input’)[0].onblur = function() { socket.send(this.value); };</script></body></html>node服务端文件var http = require(‘http’);var socket = require(‘socket.io’);// 启http服务var server = http.createServer(function(req, res) { res.writeHead(200, { ‘Content-type’: ’text/html’ }); res.end();});server.listen(‘8080’);console.log(‘Server is running at port 8080…’);// 监听socket连接socket.listen(server).on(‘connection’, function(client) { // 接收信息 client.on(‘message’, function(msg) { client.send(‘hello:’ + msg); console.log(‘data from client: —> ’ + msg); }); // 断开处理 client.on(‘disconnect’, function() { console.log(‘Client socket has closed.’); });});2.4、Node中间件代理实现原理:同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。主要访问路径客户端发出请求代理服务接受客户端请求 。大理服务将请求 转发给应用服务器。应用服务器接收到请求代理服务器求情 ,响应数据。代理服务器将响应数据转发给客户端。实现代码:前端代码示例:<!– index.html 文件 –><!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <title></title></head><body><h1>1111</h1><script src=“https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script><script> $.ajax({ url: ‘http://127.0.0.1:3000/login?user=admin&password=123’, success: function(result) { console.log(result) }, error: function(msg) { console.log(msg) } })</script></body></html>代理服务器var express = require(’express’);var proxy = require(‘http-proxy-middleware’);var app = express();var options = { dotfiles: ‘ignore’, etag: false, extensions: [‘htm’, ‘html’], index: false, maxAge: ‘1d’, redirect: false, setHeaders: function (res, path, stat) { res.set(‘x-timestamp’, Date.now()) }}app.use(express.static(‘public’, options))app.use(’/’, proxy({ // 代理跨域目标接口 target: ‘http://127.0.0.1:4000’, changeOrigin: true, // 修改响应头信息,实现跨域并允许带cookie onProxyRes: function(proxyRes, req, res) { res.header(‘Access-Control-Allow-Origin’, ‘http://127.0.0.1’); res.header(‘Access-Control-Allow-Credentials’, ’true’); }, // 修改响应信息中的cookie域名 cookieDomainRewrite: ‘127.0.0.1’ // 可以为false,表示不修改}));app.listen(3000);console.log(‘Proxy server is listen at port 3000…’);应用服务器// 服务器const http = require(“http”);const server = http.createServer();const qs = require(“querystring”);server.on(“request”, function(req, res) { var params = qs.parse(req.url.split(’?’)[1]); console.log(req.url, params); // 向前台写 cookie res.writeHead(200, { “Set-Cookie”: “l=a123456;Path=/;Domain=127.0.0.1;HttpOnly” // HttpOnly:脚本无法读取 }); res.write(JSON.stringify({ data: ‘I LOVE YOU’, …params })); res.end();});server.listen(“4000”);console.log(’listen 4000…’)最终效果2.5、nginx反向代理跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。nginx具体配置:#proxy服务器server { listen 81; server_name www.domain1.com; location / { proxy_pass http://www.domain2.com:8080; #反向代理 proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名 index index.html index.htm; # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用 add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为 add_header Access-Control-Allow-Credentials true; }}Nodejs后台示例:var http = require(‘http’);var server = http.createServer();var qs = require(‘querystring’);server.on(‘request’, function(req, res) { var params = qs.parse(req.url.split(’?’)[1]); // 向前台写cookie res.writeHead(200, { ‘Set-Cookie’: ’l=a123456;Path=/;Domain=www.domain2.com;HttpOnly’ // HttpOnly:脚本无法读取 }); res.write(JSON.stringify(params)); res.end();});server.listen(‘8080’);console.log(‘Server is running at port 8080…’);前端代码示例:var xhr = new XMLHttpRequest();// 前端开关:浏览器是否读写cookiexhr.withCredentials = true;// 访问nginx中的代理服务器xhr.open(‘get’, ‘http://www.domain1.com:81/?user=admin', true);xhr.send();2.6、CORS普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。简单请求只要同时满足以下两大条件,就属于简单请求1:使用下列方法之一:GET、HEAD、POST2:Content-Type 的值仅限于下列三者之一:text/plain、multipart/form-data、application/x-www-form-urlencoded复杂请求凡是不同时满足上面两个条件,就属于复杂请求。复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求,该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。我们用PUT向后台请求时,属于复杂请求,后台需做如下配置:// 允许哪个方法访问我res.setHeader(‘Access-Control-Allow-Methods’, ‘PUT’)// 预检的存活时间res.setHeader(‘Access-Control-Max-Age’, 6)// OPTIONS请求不做任何处理if (req.method === ‘OPTIONS’) { res.end() }// 定义后台返回的内容app.put(’/getData’, function(req, res) { console.log(req.headers) res.end(‘我不爱你’)})接下来我们看下一个完整复杂请求的例子,并且介绍下CORS请求相关的字段// index.htmllet xhr = new XMLHttpRequest()document.cookie = ’name=xiamen’ // cookie不能跨域xhr.withCredentials = true // 前端设置是否带cookiexhr.open(‘PUT’, ‘http://localhost:4000/getData’, true)xhr.setRequestHeader(’name’, ‘xiamen’)xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { console.log(xhr.response) //得到响应头,后台需设置Access-Control-Expose-Headers console.log(xhr.getResponseHeader(’name’)) } }}xhr.send()//server1.jslet express = require(’express’);let app = express();app.use(express.static(__dirname));app.listen(3000);//server2.jslet express = require(’express’)let app = express()let whitList = [‘http://localhost:3000’] //设置白名单app.use(function(req, res, next) { let origin = req.headers.origin if (whitList.includes(origin)) { // 设置哪个源可以访问我 res.setHeader(‘Access-Control-Allow-Origin’, origin) // 允许携带哪个头访问我 res.setHeader(‘Access-Control-Allow-Headers’, ’name’) // 允许哪个方法访问我 res.setHeader(‘Access-Control-Allow-Methods’, ‘PUT’) // 允许携带cookie res.setHeader(‘Access-Control-Allow-Credentials’, true) // 预检的存活时间 res.setHeader(‘Access-Control-Max-Age’, 6) // 允许返回的头 res.setHeader(‘Access-Control-Expose-Headers’, ’name’) if (req.method === ‘OPTIONS’) { res.end() // OPTIONS请求不做任何处理 } } next()})app.put(’/getData’, function(req, res) { console.log(req.headers) res.setHeader(’name’, ‘jw’) //返回一个响应头,后台需设置 res.end(‘我不爱你’)})app.get(’/getData’, function(req, res) { console.log(req.headers) res.end(‘我不爱你’)})app.use(express.static(__dirname))app.listen(4000)2.7、location name +iframe原理:window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在。下面a.html和b.html是同域的,都是http://localhost:3000;而c.html是http://localhost:4000// a.html(http://localhost:3000/b.html) <iframe src=“http://localhost:4000/c.html” frameborder=“0” onload=“load()” id=“iframe”></iframe> <script> let first = true // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name function load() { if(first){ // 第1次onload(跨域页)成功后,切换到同域代理页面 let iframe = document.getElementById(‘iframe’); iframe.src = ‘http://localhost:3000/b.html’; first = false; }else{ // 第2次onload(同域b.html页)成功后,读取同域window.name中数据 console.log(iframe.contentWindow.name); } } </script>b.html为中间代理页,与a.html同域,内容为空。c页面 // c.html(http://localhost:4000/c.html) <script> window.name = ‘我不爱你’ </script>总结:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。2.8、document. hash + iframe实现原理: a.html欲与c.html跨域相互通信,通过中间页b.html来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。具体实现步骤:一开始a.html给c.html传一个hash值,然后c.html收到hash值后,再把hash值传递给b.html,最后b.html将结果放到a.html的hash值中。同样的,a.html和b.html是同域的,都是http://localhost:3000;而c.html是http://localhost:4000 // a.html <iframe src=“http://localhost:4000/c.html#iloveyou”></iframe> <script> window.onhashchange = function () { //检测hash的变化 console.log(location.hash); } </script> // b.html <script> window.parent.parent.location.hash = location.hash //b.html将结果放到a.html的hash值中,b.html可通过parent.parent访问a.html页面 </script> // c.html console.log(location.hash); let iframe = document.createElement(‘iframe’); iframe.src = ‘http://localhost:3000/b.html#idontloveyou’; document.body.appendChild(iframe);2.9、 document.domain + iframe实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。该方式只能用于二级域名相同的情况下,比如 a.test.com 和 b.test.com 适用于该方式。 只需要给页面添加 document.domain =‘test.com’ 表示二级域名都相同就可以实现跨域。我们看个例子:页面a.zf1.cn:3000/a.html获取页面b.zf1.cn:3000/b.html中a的值// a.html<body> helloa <iframe src=“http://b.zf1.cn:3000/b.html" frameborder=“0” onload=“load()” id=“frame”></iframe> <script> document.domain = ‘zf1.cn’ function load() { console.log(frame.contentWindow.a); } </script></body>// b.html<body> hellob <script> document.domain = ‘zf1.cn’ var a = 100; </script></body>3、总结日常工作中,用得比较多的跨域方案是cors和nginx反向代理CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案不管是Node中间件代理还是nginx反向代理,主要是通过同源策略对服务器不加限制。SONP只支持GET请求,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。后续更多文章将在我的github第一时间发布,欢迎关注。参考浏览器同源政策及其规避方法跨域资源共享 CORS 详解前端常见跨域解决方案(全) ...

April 14, 2019 · 4 min · jiezi

大话javascript 5期:跨域

一、什么是跨域1.URL解析URL (Uniform Resource Locator )统一资源定位符(URL)是用于完整地描述Internet上网页和其他资源的地址的一种标识方法。Internet上的每一个网页都具有一个唯一的名称标识,通常称之为URL地址,这种地址可以是本地磁盘,也可以是局域网上的某一台计算机,更多的是Internet上的站点。简单地说,URL就是Web地址,俗称“网址”。URL通常由三部分组成:协议类型,主机名和路径及文件名。2.同源策略所谓同源是指协议,域名,端口均相同。如果两个页面的协议,端口(如果有指定)和主机都相同,则两个页面具有相同的源。同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。所以a.com下的js脚本采用ajax读取b.com里面的文件数据是会报错的。同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。同源策略限制以下几种行为:Cookie、LocalStorage 和 IndexDB 无法读取DOM 和 Js对象无法获得AJAX 请求不能发送3.跨域的定义跨域是指从一个域名的网页去请求另一个域名的资源。比如从www.baidu.com 页面去请求 www.google.com的资源。(但是浏览器的同源策略会限制你不能这么做,这是是浏览器对JavaScript施加的安全限制)跨域的严格一点的定义是:只要协议,域名,端口有任何一个的不同,就被当作是跨域二、跨域场景 URL 说明 是否允许通信http://www.domain.com/a.jshttp://www.domain.com/b.js 同一域名,不同文件或路径 允许http://www.domain.com/lab/c.jshttp://www.domain.com:8000/a.jshttp://www.domain.com/b.js 同一域名,不同端口 不允许 http://www.domain.com/a.jshttps://www.domain.com/b.js 同一域名,不同协议 不允许 http://www.domain.com/a.jshttp://192.168.4.12/b.js 域名和域名对应相同ip 不允许 http://www.domain.com/a.jshttp://x.domain.com/b.js 主域相同,子域不同 不允许http://domain.com/c.js http://www.domain1.com/a.jshttp://www.domain2.com/b.js 不同域名 不允许三、浏览器为什么要限制跨域访问原因就是安全问题:如果一个网页可以随意地访问另外一个网站的资源,那么就有可能在客户完全不知情的情况下出现安全问题。比如下面的操作就有安全问题:用户访问www.mybank.com ,登陆并进行网银操作,这时cookie啥的都生成并存放在浏览器用户突然想起件事,并迷迷糊糊地访问了一个邪恶的网站 www.xiee.com这时该网站就可以在它的页面中,拿到银行的cookie,比如用户名,登陆token等,然后发起对www.mybank.com 的操作。如果这时浏览器不予限制,并且银行也没有做响应的安全处理的话,那么用户的信息有可能就这么泄露了。四、为什么要跨域?既然有安全问题,那为什么又要跨域呢? 有时公司内部有多个不同的子域,比如一个是location.company.com ,而应用是放在app.company.com , 这时想从 app.company.com去访问 location.company.com 的资源就属于跨域。五、解决跨域问题的方法1、 通过jsonp跨域2、 document.domain + iframe跨域3、 location.hash + iframe跨域4、 window.name + iframe跨域5、 postMessage跨域6、 跨域资源共享(CORS)7、 nginx代理跨域8、 nodejs中间件代理跨域9、 WebSocket协议跨域1.通过jsonp跨域【1】定义:JSONP是一种跨域资源请求解决方案,利用了<script>标签的src属性没有同源限制,进行跨域请求。【2】原理:通过动态创建<script>标签,然后通过标签的src属性获取js文件的脚本,该脚本的内容是一个函数调用,参数就是服务器返回的数据,为了处理这些返回的数据,需要实现在页面定义好回调函数,本质上使用的并不是ajax技术【3】过程:网页端插入一个script标签,src指向目标api 的 url(只能是 get api,因为 script 加载 js 文件是 http get 方法)。这里做一个小改动,url后面加上 query,?callback=handle后端 api 处理函数接收到请求,发现有 callback 参数,则将参数值拿下来,得到 handle后端用 handle 包装数据,返回给浏览器,注意,返回的 content-type 必须是 text/javascript; charset=utf-8网页端 script 内容加载完成handle(data)浏览器发现内容是 js(查看 content-type),则调用js解释器执行 handle(data)【4】核心点读取 callback 名,比如 handle将数据封装在 handle 中将封装好的内容作为 js 脚本内容返回,注意 content-type 必须为 text/javascript; charset=utf-8 以便浏览器正常解析执行 js【5】配置:要实现使用JSONP跨域需要三步:第一步,动态创建一个script元素;第二步,设置script元素的src为想要跨域请求资源的url,这个url的参数callback为请求到资源后的处理函数;第三步,定义处理函数,处理返回的对象;第四步,把script元素添加到页面中var scriptEl = document.createElement(‘script’);scriptEl.src = ‘http://www.freegeoip.net/json/?callback=handleResponse';document.body.appendChild(scriptEl);function handleResponse(response) { /response的类型是Object/ alert(response.country_name);}【6】实际封装应用// jsonp.jsexport function getJSONP(url, cb) { if (url.indexOf(’?’) === -1) { url += ‘?callback=responseHandler’; } else { url += ‘&callback=responseHandler’; } // 创建script 标签 var script = document.createElement(‘script’); // 在函数内部实现包裹函数,因为要用到 cb // responseHandler 为全局变量 window.responseHandler = function (json) { try { cb(json) } finally { // 函数调用之后,移除对应的标签 script.parentNode.removeChild(script); } } script.setAttribute(‘src’, url) document.body.appendChild(script);}调用:import { getJSONP } from “../../utils/jsonp”;const onSearch = async (query) => { const url = https://api.douban.com/v2/book/search?q=${query}; getJSONP(url, e => { // 回调函数 // e 为通过jsonp获取的数据 console.log(e) })}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…’);【7】优缺点优点:它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制,JSONP可以跨越同源策略;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持在请求完毕后可以通过调用callback的方式回传结果。将回调方法的权限给了调用方。这个就相当于将controller层和view层终于分 开了。我提供的jsonp服务只提供纯服务的数据,至于提供服务以 后的页面渲染和后续view操作都由调用者来自己定义就好了。如果有两个页面需要渲染同一份数据,你们只需要有不同的渲染逻辑就可以了,逻辑都可以使用同 一个jsonp服务。缺点:它只支持GET请求而不支持POST等其它类型的HTTP请求它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。jsonp在调用失败的时候不会返回各种HTTP状态码。缺点是安全性。万一假如提供jsonp的服务存在页面注入漏洞,即它返回的javascript的内容被人控制的。那么结果是什么?所有调用这个 jsonp的网站都会存在漏洞。于是无法把危险控制在一个域名下…所以在使用jsonp的时候必须要保证使用的jsonp服务必须是安全可信的2.document.domain + iframe跨域此方案仅限主域相同,子域不同的跨域应用场景。实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。1.)父窗口:(http://www.domain.com/a.html)<iframe id=“iframe” src=“http://child.domain.com/b.html"></iframe><script> document.domain = ‘domain.com’; var user = ‘admin’;</script>2.)子窗口:(http://child.domain.com/b.html)<script> document.domain = ‘domain.com’; // 获取父窗口中变量 alert(‘get js data from parent —> ’ + window.parent.user);</script>3.location.hash + iframe跨域实现原理: a欲与b跨域相互通信,通过中间页c来实现。三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。具体实现:A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象。1.)a.html:(http://www.domain1.com/a.html)<iframe id=“iframe” src=“http://www.domain2.com/b.html" style=“display:none;"></iframe><script> var iframe = document.getElementById(‘iframe’); // 向b.html传hash值 setTimeout(function() { iframe.src = iframe.src + ‘#user=admin’; }, 1000); // 开放给同域c.html的回调方法 function onCallback(res) { alert(‘data from c.html —> ’ + res); }</script>2.)b.html:(http://www.domain2.com/b.html)<iframe id=“iframe” src=“http://www.domain1.com/c.html" style=“display:none;"></iframe><script> var iframe = document.getElementById(‘iframe’); // 监听a.html传来的hash值,再传给c.html window.onhashchange = function () { iframe.src = iframe.src + location.hash; };</script>3.)c.html:(http://www.domain1.com/c.html)<script> // 监听b.html传来的hash值 window.onhashchange = function () { // 再通过操作同域a.html的js回调,将结果传回 window.parent.parent.onCallback(‘hello: ’ + location.hash.replace(’#user=’, ‘’)); };</script>4.window.name + iframe跨域window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。1.)a.html:(http://www.domain1.com/a.html)var proxy = function(url, callback) { var state = 0; var iframe = document.createElement(‘iframe’); // 加载跨域页面 iframe.src = url; // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name iframe.onload = function() { if (state === 1) { // 第2次onload(同域proxy页)成功后,读取同域window.name中数据 callback(iframe.contentWindow.name); destoryFrame(); } else if (state === 0) { // 第1次onload(跨域页)成功后,切换到同域代理页面 iframe.contentWindow.location = ‘http://www.domain1.com/proxy.html'; state = 1; } }; document.body.appendChild(iframe); // 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问) function destoryFrame() { iframe.contentWindow.document.write(’’); iframe.contentWindow.close(); document.body.removeChild(iframe); }};// 请求跨域b页面数据proxy(‘http://www.domain2.com/b.html', function(data){ alert(data);});2.)proxy.html:(http://www.domain1.com/proxy….中间代理页,与a.html同域,内容为空即可。3.)b.html:(http://www.domain2.com/b.html)<script> window.name = ‘This is domain2 data!’;</script>总结:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。5.postMessage跨域postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:a.) 页面和其打开的新窗口的数据传递b.) 多窗口之间消息传递c.) 页面与嵌套的iframe消息传递d.) 上面三个场景的跨域数据传递用法:postMessage(data,origin)方法接受两个参数data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。origin: 协议+主机+端口号,也可以设置为”",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为”/"。1.)a.html:(http://www.domain1.com/a.html)<iframe id=“iframe” src=“http://www.domain2.com/b.html" style=“display:none;"></iframe><script> var iframe = document.getElementById(‘iframe’); iframe.onload = function() { var data = { name: ‘aym’ }; // 向domain2传送跨域数据 iframe.contentWindow.postMessage(JSON.stringify(data), ‘http://www.domain2.com’); }; // 接受domain2返回数据 window.addEventListener(‘message’, function(e) { alert(‘data from domain2 —> ’ + e.data); }, false);</script>2.)b.html:(http://www.domain2.com/b.html)<script> // 接收domain1的数据 window.addEventListener(‘message’, function(e) { alert(‘data from domain1 —> ’ + e.data); var data = JSON.parse(e.data); if (data) { data.number = 16; // 处理后再发回domain1 window.parent.postMessage(JSON.stringify(data), ‘http://www.domain1.com’); } }, false);</script>6.跨域资源共享(CORS)普通跨域请求:服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。需注意的是:由于同源策略的限制,所读取的cookie为跨域请求接口所在域的cookie,而非当前页。如果想实现当前页cookie的写入,可参考下文:nginx反向代理中设置proxy_cookie_domain 和 NodeJs中间件代理中cookieDomainRewrite参数的设置。目前,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案。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框架a.) axios设置:axios.defaults.withCredentials = trueb.) vue-resource设置:Vue.http.options.credentials = true2、 服务端设置:若后端设置成功,前端浏览器控制台则不会出现跨域报错信息,反之,说明没设成功。1.)Java后台:/ * 导入包:import javax.servlet.http.HttpServletResponse; * 接口参数中定义:HttpServletResponse response /// 允许跨域访问的域名:若有端口需写全(协议+域名+端口),若没有端口末尾不用加’/‘response.setHeader(“Access-Control-Allow-Origin”, “http://www.domain1.com”); // 允许前端带认证cookie:启用此项后,上面的域名不能为’’,必须指定具体的域名,否则浏览器会提示response.setHeader(“Access-Control-Allow-Credentials”, “true”); // 提示OPTIONS预检时,后端需要设置的两个常用自定义头response.setHeader(“Access-Control-Allow-Headers”, “Content-Type,X-Requested-With”);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’, // 允许访问的域(协议+域名+端口) /* * 此处设置的cookie还是domain2的而非domain1, * 因为后端也不能跨域写cookie(nginx反向代理可以实现), * 但只要domain2中写入一次cookie认证,后面的跨域接口都能从domain2中获取cookie, * 从而实现所有的接口都能跨域访问 */ ‘Set-Cookie’: ’l=a123456;Path=/;Domain=www.domain2.com;HttpOnly’ // HttpOnly的作用是让js无法读取cookie }); res.write(JSON.stringify(postData)); res.end(); });});server.listen(‘8080’);console.log(‘Server is running at port 8080…’);7.nginx代理跨域1、 nginx配置解决iconfont跨域浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。location / { add_header Access-Control-Allow-Origin ;}2、 nginx反向代理接口跨域跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。nginx具体配置:proxy服务器server { listen 81; server_name www.domain1.com; location / { proxy_pass http://www.domain2.com:8080; #反向代理 proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名 index index.html index.htm; # 当用webpack-dev-server等中间件代理接口访问nignx时, # 此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用 add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为 add_header Access-Control-Allow-Credentials true; }}1.) 前端代码示例:var xhr = new XMLHttpRequest();// 前端开关:浏览器是否读写cookiexhr.withCredentials = true;// 访问nginx中的代理服务器xhr.open(‘get’, ‘http://www.domain1.com:81/?user=admin', true);xhr.send();2.) Nodejs后台示例:var http = require(‘http’);var server = http.createServer();var qs = require(‘querystring’);server.on(‘request’, function(req, res) { var params = qs.parse(req.url.substring(2)); // 向前台写cookie res.writeHead(200, { ‘Set-Cookie’: ’l=a123456;Path=/;Domain=www.domain2.com;HttpOnly’ // HttpOnly:脚本无法读取 }); res.write(JSON.stringify(params)); res.end();});server.listen(‘8080’);console.log(‘Server is running at port 8080…’);8.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 }}9.WebSocket协议跨域WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是serverpush技术的一种很好的实现。原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。1.)前端代码:<div>user input:<input type=“text”></div><script src=”./socket.io.js”></script><script>var socket = io(‘http://www.domain2.com:8080’);// 连接成功处理socket.on(‘connect’, function() { // 监听服务端消息 socket.on(‘message’, function(msg) { console.log(‘data from server: —> ’ + msg); }); // 监听服务端关闭 socket.on(‘disconnect’, function() { console.log(‘Server socket has closed.’); });});document.getElementsByTagName(‘input’)[0].onblur = function() { socket.send(this.value);};</script>2.)Nodejs socket后台:var http = require(‘http’);var socket = require(‘socket.io’);// 启http服务var server = http.createServer(function(req, res) { res.writeHead(200, { ‘Content-type’: ’text/html’ }); res.end();});server.listen(‘8080’);console.log(‘Server is running at port 8080…’);// 监听socket连接socket.listen(server).on(‘connection’, function(client) { // 接收信息 client.on(‘message’, function(msg) { client.send(‘hello:’ + msg); console.log(‘data from client: —> ’ + msg); }); // 断开处理 client.on(‘disconnect’, function() { console.log(‘Client socket has closed.’); });});参考文章:前端常见跨域解决方案(全) ...

April 3, 2019 · 5 min · jiezi

JavaScript 关于'跨域'问题的延伸-CORS

同源策略在web浏览器中,同源策略 限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。如果两个页面的 1.协议 2.端口(如果有指定)3.域名都相同,则两个页面具有相同的源。举例说明 http://store.example.com/dir/page.html 同源检测的示例:URL结果原因http://store.example.com/index.html成功 http://store.example.com/dir/another.html成功 https://store.example.com/index.html失败不同协议 ( https和http )http://store.example.com:81/index.html失败不同端口 ( 81和80)http://news.example.com/index.html失败不同域名 ( news和store )跨源场景:跨源网络访问 同源策略控制了不同源之间的交互。 问:如何允许跨源访问?答:使用 CORS 允许跨源访问。场景: 由浏览器发起的跨域 HTTP 请求都用的这个,例如大部分的前端发出接口请求跨源脚本API访问 Javascript的APIs中,允许文档间直接相互引用。但是当两个文档的源不同时,一些引用方式将对 API对象的访问添加限制 为了在不同源中文档进一步交流,可以使用window.postMessage场景: 例如<iframe>嵌套的时候,父子页面的通信跨源数据存储访问 存储在浏览器中的数据,如localStorage和IndexedDB,以源进行分割。每个源都拥有自己单独的存储空间,一个源中的Javascript脚本不能对属于其它源的数据进行读写操作。了解CORSCORS是什么?MDN的网站给出了这样的解释:CORS (Cross-Origin Resource Sharing,跨域资源共享)是一个系统,它由一系列传输的HTTP头组成,这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应。 CORS 给了web服务器这样的权限,即服务器可以选择,允许跨域请求访问到它们的资源。可以这样理解:CORS赋予服务端(通常所说的后端)一个权力,让它可以自己控制哪一些浏览器的请求可以访问到它的资源,来解决跨域问题。附:所有的 CORS 头HTTP头功能Access-Control-Allow-Origin指示请求的资源能共享给哪些域。Access-Control-Allow-Credentials指示当请求的凭证标记为 true 时,是否响应该请求。Access-Control-Allow-Headers用在对预请求的响应中,指示实际的请求中可以使用哪些 HTTP 头。Access-Control-Allow-Methods指定对预请求的响应中,哪些 HTTP 方法允许访问请求的资源。Access-Control-Expose-Headers指示哪些 HTTP 头的名称能在响应中列出。Access-Control-Max-Age指示预请求的结果能被缓存多久。Access-Control-Request-Headers用于发起一个预请求,告知服务器正式请求会使用那些 HTTP 头。Access-Control-Request-Method用于发起一个预请求,告知服务器正式请求会使用哪一种 HTTP 请求方法。Origin指示获取资源的请求是从什么域发起的。CORS 功能概述TL;DR规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。一句话概述:非简单请求时,会先发送预检请求,允许后再发送实际请求附:node-express框架下,服务端的跨域设置app.all(’’, function (req, res, next) { res.header(‘Access-Control-Allow-Origin’, ‘’); res.header(‘Access-Control-Allow-Headers’, ‘Content-Type’); res.header(‘Access-Control-Allow-Methods’, ‘*’); next();});CORS 关于CookieCORS请求默认不发送Cookie和HTTP认证信息,如果想要知道用cookie就要注意3点Client端 new XMLHttpRequest() 中 withCredentials 设置为 trueSerive端 HTTP头 Access-Control-Allow-Credentials 设置为 trueSerive端 HTTP头 Access-Control-Allow-Origin 不能设为星号,必须指定明确的、与请求网页一致的域名所以上面的例子要想发送cookie// service端res.header('Access-Control-Allow-Credentials', true);``res.header('Access-Control-Allow-Origin', '具体的域名');// client端Jquery ajax() xhrFields: {withCredentials: true}Axios axios.defaults.withCredentials = trueCORS 的简单请求上面讲了简单请求,但那些才是简单请求呢?答:若不会触发 CORS 的预检请求,称这样的请求为“简单请求”以下为简单请求:HTTP Method 组成只能是以下几种GETPOSTHEADHTTP Headers 组成AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type 只包含以下类型 (form表单请求)application/x-www-form-urlencodedmultipart/form-datatext/plain注:只有同时满足以上两个条件时,才是简单请求,否则为非简单请求CORS 预检请求又是什么?前面说了这么多预检请求,我们来讲一讲什么是预检请求:如果我们在client端发送请求时,例如:// 原生var invocation = new XMLHttpRequest();invocation.setRequestHeader(‘X-EXAMPLE’, ‘xixihaha’);invocation.setRequestHeader(‘Content-Type’, ‘application/xml’);// axiosaxios.defaults.headers[‘X-EXAMPLE’] = ‘xixihaha’;axios.defaults.headers[‘Content-Type’] = ‘application/xml’;POST 请求发送一个 XML 文档,该请求包含了一个自定义的请求首部字段(X-EXAMPLE: xixihaha)。另外,该请求的 Content-Type 为 application/xml。因此,该请求需要首先发起“预检请求”。server端 的HTTP头设置Access-Control-Allow-Origin: ‘具体的域名’Access-Control-Allow-Methods: POST, GET, OPTIONS // 可包含的参数Access-Control-Allow-Headers: X-PINGOTHER, Content-Type // 允许的首部字段Access-Control-Max-Age: 86400非简单请求和简单请求无异,如果浏览器的预检请求被服务器接受,则发送实际请求,未被接受则拒绝请求。其他JSONP动态创建script标签,然后利用script的src 不受同源策略约束来跨域获取数据function addScriptTag() { var script = document.createElement(“script”); script.src = “http://foo.example?callback=handleResponse"; document.body.appendChild(script);}function handleResponse() { console.log(‘跨域数据’);};以下跨域方案不做过多解释上文提到的 postMessage()nginx转发,即架设服务器代理window.namedocument.domain 参考阮一峰的网络日志MDN web docs CORS ...

March 6, 2019 · 1 min · jiezi

CORS:跨域资源共享

定义是由W3C提出的一个用于浏览器以XMLHttpRequest方式向其他源的服务器发起请求的规范。不同于JSONP,CORS是以Ajax方式进行跨域请求,需要服务端与客户端的同时支持。目前CORS在绝大部分现代浏览器中都是支持的(IE浏览器不能低于10)原理CORS标准定义了一个规范的HTTPHeaders来使得浏览器与服务端之间可以进行协商来确定某个资源是否可以由其他域的客户端请求获得。尽管很多的验证与鉴权是由服务端完成,但是本质上大部分的检查和限制还是应该由浏览器完成。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。一般来说CORS会分为简单请求与预检请求两大类。预检请求当浏览器的请求方式满足以下的任意一个条件的时候,浏览器会先发送一个OPTION请求,用来与目标域名服务器协商决定是否可以发送实际的跨域请求。OPTIONS请求头部中会包含以下头部:Origin、Access-Control-Request-Method、Access-Control-Request-Headers。服务器收到OPTIONS请求后,设置Access-Control-Allow-Origin、Access-Control-Allow-Method、Access-Control-Allow-Headers头部与浏览器沟通来判断是否允许这个请求。请求方法不是下列之一:GETHEADPOST请求头中的Content-Type请求头的值不是下列之一:application/x-www-form-urlencodedmultipart/form-datatext/plain否则就是预检请求。预检请求会在正式通信之前,增加一次HTTP查询请求。浏览器会先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。预检请求的发送请求:“预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。请求头信息里面,关键字段是Origin,表示请求来自哪个源。除了Origin字段,“预检"请求的头信息包括两个特殊字段:Access-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是GET。Access-Control-Request-Headers:该字段指定浏览器CORS请求会额外发送的头信息字段.预检请求的返回:Access-Control-Allow-Methods:必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次预检请求。Access-Control-Allow-Headers:如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在预检中请求的字段。Access-Control-Max-Age:该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是5个小时(18000秒),即允许缓存该条回应18000秒(即5小时),在此期间,不用发出另一条预检请求。一旦服务器通过了预检请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。简单请求当浏览器的请求方式是HEAD、GET或者POST,并且HTTP的头信息中不会超出以下字段:AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain浏览器会将该请求定义为简单请求,对于简单的跨域请求或者通过了预检的请求,浏览器会自动在请求的头信息加上Origin字段,表示本次请求来自哪个源(协议 + 域名 + 端口),即表明这是一个跨域请求。服务端会获取到这个值,根据相应的跨域规则然后判断是否同意这次请求并返回。典型的请求头尾:// 请求 - GET /cors HTTP/1.1 - Origin: http://localhost:8080 - Host: api.alice.com - Accept-Language: en-US - Connection: keep-alive - User-Agent: Mozilla/5.0…如果服务端允许,在返回的头信息中会多出几个字段:// 返回 Access-Control-Allow-Origin: http://localhost:8080 Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: Info Content-Type: text/html; charset=utf-8Access-Control-Allow-Origin:必须。它的值是请求时Origin字段的值或者 ,表示接受任意域名的请求。Access-Control-Allow-Credentials:可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。 再需要发送cookie的时候还需要注意要在AJAX请求中打开withCredentials属性:withCredentials = true;需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且原网页代码中的document.cookie也无法读取服务器域名下的Cookie。Access-Control-Expose-Headers:可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方 法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last- Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader(‘Info’)可以返回Info字段的值。如果服务端拒绝了调用,即不会带上 Access-Control-Allow-Origin 字段,浏览器发现这个跨域请求的返回头信息没有该字段,就会抛出一个错误,会被 XMLHttpRequest 的 onerror 回调捕获到。这种错误无法通过 HTTP 状态码判断,因为回应的状态码有可能是200。总结服务器端对于跨域请求的处理流程如下:首先会检测http请求头是否有origin字段;如果没有,或者不允许,直接当成普通请求处理,结束;如果有并且是允许的,那么再看是否是预检请求如果不是预检请求,就返回Allow-Origin、Allow-Credentials等,并返回正常内容。如果是预检请求,就返回Allow-Headers、Allow-Methods等,内容为空。

February 21, 2019 · 1 min · jiezi

前端如何通过Nginx代理做到跨域访问API接口

跨域是指a页面想获取b页面资源,如果a、b页面的协议、域名、端口、子域名不同,或是a页面为ip地址,b页面为域名地址,所进行的访问行动都是跨域的,而浏览器为了安全问题一般都限制了跨域访问,也就是不允许跨域请求资源Nginx作为反向代理服务器,就是把http请求转发到另一个或者一些服务器上。通过把本地一个url前缀映射到要跨域访问的web服务器上,就可以实现跨域访问。对于浏览器来说,访问的就是同源服务器上的一个url。而nginx通过检测url前缀,把http请求转发到后面真实的物理服务器一.配置Nginx废话不多说,我们直接打开nginx.conf文件server { listen 8888; server_name 127.0.0.1; location / { proxy_pass http://127.0.0.1:5500; } location /api{ proxy_pass http://ip.taobao.com/; } }配置解释:我们在浏览器中输入 127.0.0.1:8888 自动会转发到 http://127.0.0.1:5500http://127.0.0.1:5500 是本地所指向的地址,类似于vue开的的代理npm run dev 启动的一个地址http://ip.taobao.com/ 是我们要访问的接口地址(淘宝检测ip地址来源的接口)前端ajax的url地址 这样写 http://127.0.0.1:8888/api/service/getIpInfo.php?ip=117.89.35.51,访问的url中有api nginx会自动换到所对应的location前端实列代码://新建一个html文件把以下代码放入script标签中$.ajax({ //请求淘宝检测ip地址来源的接口 url:‘http://127.0.0.1:8888/api/service/getIpInfo.php?ip=117.89.35.51’, type:‘get’, success:function(res){ console.log(res) }, error:function(err){ console.log(err) }})启动服务:我是通过vsCode的Go live插件启动了一个127.0.0.1:5500的服务,有很多同学是通过node开启的代理,都一样,然后我们在浏览器中输入127.0.0.1:8888上面nginx所配置打开浏览器network数据返回如下,说明跨域访问成功二.其它跨域解决方案1.jsonp 需要目标服务器配合一个callback函数。2.window.name+iframe 需要目标服务器响应window.name。3.html5的 postMessage+ifrme 这个也是需要目标服务器或者说是目标页面写一个postMessage,主要侧重于前端通讯。4.node.js开启本地代理,类似于vue-cli中的devServer模式,阔以方便开启代理5.CORS 需要服务器设置header :Access-Control-Allow-Origin。6.Nginx反向代理,可以不用目标服务器配合,需要Nginx服务器,用于请求转发。我个人认为4 、5 、6解决跨域问题在实际开发过程中显得更为重要三.Nginx工具以及参考资料Nginx在线配置生成工具(需要翻墙)如何提高Nginx的性能Nginx常用命令Nginx 配置简述(小胡子哥)

February 15, 2019 · 1 min · jiezi

利用Nginx反向代理解决跨域问题

问题在之前的分享的跨域资源共享的文章中,有提到要注意跨域时,如果要发送Cookie,Access-Control-Allow-Origin就不能设为*,必须指定明确的、与请求网页一致的域名。在此次项目开发中与他人协作中就遇到此类问题。解决思路一般来说,与后台利用CORS跨域资源共享将Access-Control-Allow-Origin设置为访问的域名即可,这个需要后台的配合,且有些浏览器是不支持的。基于与合作方后台的配合,利用nginx方向代理来满足浏览器的同源策略来实现跨域实现方法反向代理概念反向代理(Reverse Proxy)方式是指以代理服务器来接受Internet上的连接请求,然后将请求转发给内部网络上的服务器;并将从服务器上得到的结果返回给Internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器。反向代理服务器对于客户端而言它就像是原始服务器,并且客户端不需要进行任何特别的设置。客户端向反向代理的命名空间(name-space)中的内容发送普通请求,接着反向代理将判断向何处(原始服务器)转交请求,并将获得的内容返回给客户端,就像这些内容原本就是它自己的一样。利用nginx反向代理实现跨域的步骤去nginx官网下载包搭建nginx环境修改nginx的配置文件,找到ngixn.conf文件,修改相关配置http { include mime.types; default_type application/octet-stream; sendfile on; server { listen 8000; #监听8000端口,可以改成其他端口 server_name localhost; # 当前服务的域名 location /wili/api/ { proxy_pass http://chick.platform.deva.wili.us/api/; #添加访问路径录为/will/api的代理配置 proxy_http_version 1.1; } location / { proxy_pass http://localhost:8001; proxy_http_version 1.1; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } }}配置的解释:由配置信息可知,我们让nginx监听localhost的8000端口,网站A与网站B的访问都是经过localhost的8000端口进行访问。我们特殊配置了一个"/will/api"的访问,使以"will/api”开头的地址都转到"http://chick.platform.deva.wili.us/api/“进行处理。访问地址修改既然我们已经配置了nginx,那么所有的访问都要走nginx,而不是走网站原本的地址(A网站localhost:8001,B网站http://chick.platform.deva.wi…)。所以要修改A网站中的请求接口换成http://localhost:8000/wili/api/。接下来启动nginx,访问配置的8000即可需要注意的一点是nginx启动可能会冲突端口造成启动不成功,可在任务管理器查看是否启动成功。总结浏览器跨域的解决方式有很多种:jsonp 需要目标服务器配合一个callback函数CORS需要服务器设置header:Access-Control-Allow-Originnginx反向代理 这个方法一般很少有人提及,但是他可以不用目标服务器配合,不过需要你搭建一个中转nginx服务器,用于转发请求。(使用反向代理可能访问网页相对于之前响应会比较慢)

January 31, 2019 · 1 min · jiezi

解锁跨域的九种姿势

解锁跨域的九种姿势作者: Pawn 时间: 2019.01.24本文首发: Pawn博客github: https://github.com/LiChangyi描述:分析跨域,解锁跨域的九种姿势。写在前面针对本文的九种方法我均写的有相应的demo演示(对应的前端文件,后端文件和配置文件),强烈建议不熟悉的朋友都去尝试一下。 本文github地址,fontService是前端地址文件,service是后端文件。网络上存在很多不同的跨域文章,我在学习的时候基本上也是去看他们的文章,但是有些地方的确理解起来有点困难,所以本文就这样产生了,希望能写一点和现在网络上文章中都不一样的东西。同时也把我自己的看法写进去,和大家相互交流自己的看法。跨域在以前一直折磨着每个前端开发者。但是现在,三大框架的普及,我们在开发的过程中,只修改小小的配置,便没有这些顾虑。但实质上还是webpack-dev-server已经帮我们处理了这一个问题,把当前请求,代理到目标服务器上面,然后把获取到的数据返回。所以,现在很多前端开发者,包括我在写这篇文章之前,对跨域都不是很了解,只有一个个模模糊糊的概念,只会用jsonp去获取,或者直接用jq的jsonp方法去解决跨域,都不会去了解为什么这样就能解决跨域?甚至,很多人对跨域都已经放弃了,因为三大框架的学习,完善的脚手架功能,简化了我们,项目部署也有后端的同学帮我们解决。但是个人认为跨域是每一个前端开发者需要必备的能力,不论是跨域获取数据,还是后面进行ssr服务端渲染的配置都需要了解一点跨域,了解一点请求的代理。为什么存在跨域我们在解决的一个问题的同时我们应该先去了解这个问题是如何产生的。之所以要使用跨域,最终的罪魁祸首都是浏览器的同源策略,浏览器的同源策略限制我们只能在相同的协议、IP地址、端口号相同,如果有任何一个不通,都不能相互的获取数据。这里注意一下,http和https之间也存在跨域,因为https一般采用的是443端口,http采用的是80端口或者其他。这里也存在端口号的不同。想要详细了解请看 => MDN对浏览器的同源策略的说明虽然同源策略的确很可恶,但是如果没有同源策略用户将会陷入很境界。比如,你正在吃着火锅哼着歌,逛着淘宝买东西,但是这时你的同学给你发了一个网址,然后你直接打开来看,假如没有同源策略,他在该网站中用一个iframe的标签然后把src指向淘宝网,这时没有同源策略,他便可以直接通过js操作iframe的页面,比如说获取cookie或者用js模拟点击这些操作(因为你已经登录过了,可以不用再次登录就点击了),危害是很大的。大家可以先去做一个小测验,你在本地创建一个a.html和b.html2个文件,然后在a.html中用iframe中插入b.html,你会发现当前2个页面是一个域的,你可以在a中通过js控制b,和在b中直接用js操作没有区别。但是如果你插入的不是同一个域下面的页面,比如你插入的是淘宝,你会发现你不能通过js操作,你console.log(iframe.contentWindow)你会发现只有少数的几项。大家可以去看看大佬的文章:浅谈CSRF攻击方式,虽然没有永远牢靠的盾,但是有同源策略的存在,会让攻击的成本变得高一点。虽然同源策略危害很大,但是我们还是在一定的场景下面需要进行跨域处理,比如说百度全家桶,你在百度地图和百度搜索2者之间肯定是放在2个域下面的(我没有具体的去了解,但是我猜想肯定是这样的)。在开发地图的时候假如需要应用搜索的时候就不得不用跨域了。比如:百度搜索,输入文字出现内容提示,如果我没有判断错误就是采用的jsonp来做得跨域。大家在学习了jsonp跨域的时候,可以去尝试去获取一下。进入正题1.JSONP说起如何去解决跨域,我相信每个人脑袋中跳出来的第一个词就是jsonp。因为浏览器的同源策略不限制script、link、img三个标签,比如我们经常用这三个标签来加载其他域的资源,我个人的看法这就已经算是跨域了。jsonp的跨域就是应用script标签可以进行获取远程的js脚本文件。// 比如1.js 脚本文件say(‘haha’);我在html里面引入1.js文件,那么他讲会执行say函数,我们需要传入的数据就是haha。所以jsonp的方法就是动态的创建一个script标签,然后设置src为我们需要进行跨域的地址。当然这个方法需要后台的设置。大家可以看我写的代码,前端文件在fontEndService/www/demo1/index,htmlbtn.onclick = () => { jsonp(‘http://127.0.0.1:8888/api/getdata?jsonp=displayData’);}function jsonp(url) { let script = document.createElement(‘script’); script.setAttribute(‘src’, url); document.getElementsByTagName(‘head’)[0].appendChild(script);}function displayData(data) { msg.innerText = JSON.stringify(data);}然后后端代码是用koa写的一个简约的接口,文件在service/app.js// 简约的后端代码,我们直接调用前端传过来的需要执行的函数router.get(’/api/getdata’, ctx => { const params = get_params(ctx.request.url) const data = { title: ‘数据获取’, list: [0, 1, 2] } ctx.body = ${params.jsonp || 'callback'}(${JSON.stringify(data)});})前端通过script标签给后台发送一个get请求,在jsonp=displayData(一个我们接受到数据然后执行的方法,该方法是前端的),当我后台接受到请求后,就返回一个,执行displayData这个方法的脚本。然后把我们需要传递的数据放在形参里面。这样就相当于我们在前端里面执行displayData这个方法。用这个方法来实现跨域资源的共享。此方法是比较常用的一个方法,简单粗暴。但是此方法有一个致命的缺点就是只支持GET请求。所以说如果前端页面仅仅是作为页面的展示,就只获取数据的话,只用此方法就没有任何问题。2.iframe+document.domain这个方法,个人感觉不是特别的常用,因为这个跨域方法要求2个域之间的主域名相同,子域不同,比如a.xxx.com和b.xxx.com。如果不同的话是不行的。此方法的思想就是设置页面的document.domain把他们设置成相同的域名,比如都设置成xxx.com。这样来绕过同源策略。很简单的一个方法,具体的代码文件请看github。代码里面的测试案列是,前端文件在7777端口,后台文件在8888端口,前端如果需要请求后端的数据就存在跨域,所以我们在后端8888端口写一个提供数据的中转html,然后通过ajax或者其他的方法请求到数据,然后把数据往外暴露。此方法需要2个html都需要设置相同的主域。3.iframe+location.hash这种方法是一个很奇妙的方法,虽然我感觉很鸡肋,但是它的实现方法很巧妙,我在学习的时候都感觉到不可思议,还能这么玩?首先我们需要了解hash是什么?比如有一个这样的url:http://www.xxx.com#abc=123,那么我们通过执行location.hash就可以得到这样的一个字符串#abc=123,同时改变hash页面是不会刷新的。这一点,相信很多学习三大框架的朋友应该都见识过hash路由。所以说我们可以根据这一点来在#后面加上我们需要传递的数据。加入现在我们有A页面在7777端口(前端显示的文件),B页面在8888端口,后台运行在8888端口。我们在A页面中通过iframe嵌套B页面。从A页面要传数据到B页面我们在A页面中通过,修改iframe的src的方法来修改hash的内容。然后在B页面中添加setInterval事件来监听我们的hash是否改变,如果改变那么就执行相应的操作。比如像后台服务器提交数据或者上传图片这些。从B页面传递数据到A页面经过上面的方法,那么肯定有聪明的朋友就在想那么,从B页面向A页面发送数据就是修改A页面的hash值了。对没错方法就是这样,但是我在执行的时候会出现一些问题。我们在B页面中直接:parent.location.hash = “#xxxx"这样是不行的,因为前面提到过的同源策略不能直接修改父级的hash值,所以这里采用了一个我认为很巧妙的方法。部分代码:try { parent.location.hash = message=${JSON.stringify(data)};} catch (e) { // ie、chrome 的安全机制无法修改parent.location.hash, // 利用一个中间html 的代理修改location.hash // 如A => B => C 其中,当前页面是B,AC在相同的一个域下。B 不能直接修改A 的 hash值故修改 C,让C 修改A // 文件地址: fontEndService/www/demo3/proxy.html if (!ifrProxy) { ifrProxy = document.createElement(‘iframe’); ifrProxy.style.display = ’none’; document.body.appendChild(ifrProxy); } ifrProxy.src = http://127.0.0.1:7777/demo3/proxy.html#message=${JSON.stringify(data)};}如果可以直接修改我们就直接修改,如果不能直接修改,那么我们在B页面中再添加一个iframe然后指向C页面(我们暂时叫他代理页面,此页面和A页面是在相同的一个域下面),我们可以用同样的方法在url后面我们需要传递的信息。在代理页面中:parent.parent.location.hash = self.location.hash.substring(1);只需要写这样的一段js代码就完成了修改A页面的hash值,同样在A页面中也添加一个setInterval事件来监听hash值的改变。我们现在再来理一下思路。我们现在有三个页面,A,B,C。A页面是我们前端显示的页面;B页面我们可以把他当做A页面也后端数据交互的一个中间页面,完成接受A的数据和向后台发送请求。但是由于同源策略的限制我们不能在B页面中直接修改A的hash值,所以我们需要借助一个与A页面在同一个域名下的C页面。C页面我们把他当中一个代理页面,我们因为他和A页面在一个域下,所以可以修改A的hash值。所以B页面修改C页面的hash值,然后C页面修改A页面的hash值。(C就是一个打工的)此方法虽然我个人感觉实现的思路很巧妙但是,使用价值似乎不高,因为他实现的核心思路就是通过修改URL的hash值,然后用定时器来监听值的改变来修改。所以说最大的问题就是,我们传递的数据会直接在URL里面显示出来,不是很安全,同时URL的长度是一定的所以传输的数据也是有限的。4.iframe+window.name相比于前面2种iframe的方法,这种方法的使用人数要多一点。因为他有效的解决了前面2种方法很大的缺点。这种方法的原理就是window.name属性在于加载不同的页面(包括域名不同的情况下),如果name值没有修改,那么它将不会变化。并且这个值可以非常的长(2MB)方法原理:A页面通过iframe加载B页面。B页面获取完数据后,把数据赋值给window.name。然后在A页面中修改iframe使他指向本域的一个页面。这样在A页面中就可以直接通过iframe.contentWindow.name获取到B页面中获取到的数据。A页面中部分代码:let mark = false;let ifr = document.createElement(‘iframe’);ifr.src = “http://127.0.0.1:8888/demo4”;ifr.style.display = ’none’;document.body.appendChild(ifr);ifr.onload = () => { // iframe 中数据加载完成,触发onload事件 if (mark) { msg.innerText = ifr.contentWindow.name;// 这就是数据 document.body.removeChild(ifr); mark = false; } else { mark = true; // 修改src指向本域的一个页面(这个页面什么都没有) ifr.contentWindow.location = “http://127.0.0.1:7777/demo4/proxy.html”; }}5.postMessagepostMessage是HTML5引入的API。他可以解决多个窗口之间的通信(包括域名的不同)。我个人认为他算是一种消息的推送,可以给每个窗口推送。然后在目标窗口添加message的监听事件。从而获取推送过来的数据。A页面中部分代码:<iframe id=“iframe” src=“http://127.0.0.1:8888/demo5” frameborder=“0”></iframe>iframe.contentWindow.postMessage(‘getData’, ‘http://127.0.0.1:8888’);// 监听其他页面给我发送的数据window.addEventListener(‘message’, e => { if (e.origin !== ‘http://127.0.0.1:8888’) return; msg.innerText = e.data;})这里我们给目标窗口127.0.0.1:8888推送了getData的数据。然后在B页面中添加事件的监听。B页面中部分代码:window.addEventListener(‘message’, e => { // 判断来源是不是我们信任的站点,防止被攻击 if (e.origin !== ‘http://127.0.0.1:7777’) return; const data = e.data; if (data === ‘getData’) { // 根据接受到的数据,来进行下一步的操作 myAjax(’/api/getdata’, notice); }})function notice(data) { // 向后台请求到数据以后,在向父级推送消息 top.postMessage(JSON.stringify(data), ‘http://127.0.0.1:7777’)}我个人认为这种方式就是一个事件的发布与监听,至少说可以无视同源策略。6.cors其实对于跨域资源的请求,浏览器已经把我们的请求发放给了服务器,浏览器也接受到了服务器的响应,只是浏览器一看我们2个的域不一样就把消息给拦截了,不给我们显示。所以我们如果我们在服务器就告诉浏览器我这个数据是每个源都可以获取就可以了。这就是CORS跨域资源共享。在后台代码中我以KOA列子const Koa = require(‘koa’);const router = require(‘koa-router’)();// 引入中间件const cors = require(‘koa2-cors’);const app = new Koa();// 根据后台服务器的类型,打开跨域设置// cors安全风险很高,所以,实际线上的配置肯定要比这个更加复杂,需要根据自己的需求来做app.use(cors());router.get(’/api/getdata’, ctx => { ctx.body = { code: 200, msg: ‘我是配置有cors的服务器传输的数据’ }})app.use(router.routes(), router.allowedMethods());console.log(‘配置有cors的服务器地址在:http://127.0.0.1:8889’);app.listen(8889);这样的话,任何源都可以通过AJAX发起请求来获取我们提供的数据。针对不同语言的服务器后端有不一样的处理方法,但是实质是一样的。配置了CORS的浏览器请求响应信息跨域请求响应信息7.NGINX采用nginx做代理应该是目前跨域解决方案最好的一种。现在强调前后端分离,前端根据后端提供的接口进行数据的交互然后渲染页面。在前端三大框架的同时,开发过程中不需要我们针对跨域配置很多。在网页上线以后。我们经常采用nginx来加载静态的资源,我们把我们前端打包好的文件放到nginx的目录下面,让nginx来处理客服端的静态资源的请求。然后后端部署到另外一个端口号上面,当我们需要进行数据的交互的时候,通过nginx代理把后端数据代理到前端页面。这样的步骤是相较于传统的跨域是最简单也是最有效的一种方法,因为nginx又没有同源策略。不用考虑什么兼容性也不用考虑数据大小。我们在服务器(或者测试代码的时候在本地)安装nginx服务,然后找到我们nginx的配置文件,添加以下配置文件:server { # 把页面部署的端口 listen 8080; # 静态页面存放的目录 root /var/www/html; index index.html index.htm index.php; # 只代理 /api 开头的接口,其他接口不代理 location /api/ { # 需要代理的地址, 输入我们的后台api地址 proxy_pass http://127.0.0.1:8888; }}这样,我们可以代理不同url开头的请求到不同的后端进行处理,对以后服务器做负载均衡和反向代理也很简单。8.nodejs其实这种办法和上一种用nginx的方法是差不多的。都是你把请求发给一个中间人,由于中间人没有同源策略,他可以直接代理或者通过爬虫或者其他的手段得到想到的数据,然后返回(是不是和VPN的原理有点类似)。当然现在常见的就是用nodejs作为数据的中间件,同样,不同的语言有不同的方法,但是本质是一样的。我上次自己同构自己的博客页面,用react服务器端渲染,因为浏览器的同源策略,请求不到数据,然后就用了nodejs作为中间件来代理请求数据。部分代码:const Koa = require(‘koa’);// 代理const Proxy = require(‘koa-proxy’);// 对以前的异步函数进行转换const Convert = require(‘koa-convert’);const app = new Koa();const server = require(‘koa-static’);app.use(server(__dirname+"/www/”,{ extensions: [‘html’]}));app.use(Convert(Proxy({ // 需要代理的接口地址 host: ‘http://127.0.0.1:8888’, // 只代理/api/开头的url match: /^/api//})));console.log(‘服务运行在:http://127.0.0.1:7777’);app.listen(7777);是不是和nginx很类似呀!!9.webSocketwebSocket大家应该都有所耳闻,主要是为了客服端和服务端进行全双工的通信。但是这种通信是可以进行跨端口的。所以说我们可以用这个漏洞来进行跨域数据的交互。我个人认为,这种方法实质上和postMessage差不多,但是不是特别的常用吧!(仅仅个人看法)所以说我们可以很轻松的构建基于webSocket构建一个客服端和服务端。代码在github建议大家都多多去运行一下,了解清楚。这里就不贴了。最后哇!长长短短的写了5000多字终于写到最后了!!写总结真的比写代码还困难。个人觉得,第1种方法和第6种方法是以前常用的一种方法,毕竟以前基本上都是刀耕火种的前端时代。然后2,3,4种方法基本上现在很少有人会用,包括我没去详细了解之前也不会,但是里面有很多思想却值得我们去思考,比如第3种方法,反正我个人觉得很巧妙。第5,9种个人认为,这2种方法虽然可以解决跨域,但是把他们用在跨域有点大材小用了解就好。第7,8种方法,觉得应该是现在每个前端er都应该掌握的方法,应该以后解决跨域的主要方法。所有的代码都在github上面,地址在文章开头,我强烈建议都clone下来跑一边代码,最好是结合自己的理解把9种方法都去实现一下。由于我也是才了解跨域不久,本文有很多地方仅仅是我个人看法,欢迎大佬补充、勘误! ...

January 25, 2019 · 2 min · jiezi

[心得]SpringBoot使用addCorsMappings配置跨域的坑

什么是跨域问题这里我就不说了,直接说我使用addCorsMappings方法配置跨域时遇到的问题。具体代码如下:public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/") .allowedOrigins("") .allowedMethods(“POST”, “GET”, “PUT”, “OPTIONS”, “DELETE”) .allowCredentials(true) .allowedHeaders("") .maxAge(3600);}但是使用此方法配置之后再使用自定义拦截器时跨域相关配置就会失效。原因是请求经过的先后顺序问题,当请求到来时会先进入拦截器中,而不是进入Mapping映射中,所以返回的头信息中并没有配置的跨域信息。浏览器就会报跨域异常。正确的解决跨域问题的方法时使用CorsFilter过滤器。代码如下:private CorsConfiguration corsConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); * 请求常用的三种配置,代表允许所有,当时你也可以自定义属性(比如header只能带什么,只能是post方式等等) / corsConfiguration.addAllowedOrigin(""); corsConfiguration.addAllowedHeader(""); corsConfiguration.addAllowedMethod("*"); corsConfiguration.setAllowCredentials(true); corsConfiguration.setMaxAge(3600L); return corsConfiguration;}@Beanpublic CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/", corsConfig()); return new CorsFilter(source);}参考资料:1、【SpringMVC】与权限拦截器冲突导致的Cors跨域设置失效问题2、springboot web跨域访问问题解决–addCorsMappings和CorsFilter

January 25, 2019 · 1 min · jiezi

跨域

什么是跨域一般来说,当一个请求url的协议、域名、端口三者之间任意一个与当前页面地址不同即为跨域。原因跨域产生的条件:浏览器同源策略限制浏览器是从两个方面去做这个同源策略的:DOM同源策略。禁止对不同源的页面的DOM进行操作,主要包括iframe、canvas之类的。不同源的iframe禁止数据交互的,含有不同源数据的canvas会受到污染而无法进行操作。XmlHttpRequest同源策略。简单来说就禁止不同源的AJAX请求,主要用来防止CSRF攻击。同源策略同源是指“三个相同”:协议相同域名相同端口相同同源策略的限制存储在浏览器中的数据,如localStroage、Cookie和IndexedDB不能通过脚本跨域访问不能通过脚本操作不同域下的DOM不能通过ajax请求不同域的数据在浏览器中,script,img、iframe、link等标签都可以加载跨域资源,而不受同源限制,但浏览器限制了JavaScript的权限使其不能读、写加载的内容。跨域的解决方案CookieJSONPCORSwindow.domainwindow.nameWebSocket

January 23, 2019 · 1 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

彻底弄懂跨域问题

跨域,老生常谈的问题简述作为一只前端菜鸟,跨域方面只懂得JSONP和CORS,并未曾深入了解。但随着春招越来越近,就算是菜鸟也要猛振翅膀。近几日仔细研究了跨域问题,写下这篇文章,希望对开发者们有所帮助。在读本文前,希望您对以下知识略有了解。浏览器同源策略nodejsiframedocker, nginx我们为何要研究跨域问题因为浏览器的同源策略规定某域下的客户端在没明确授权的情况下,不能读写另一个域的资源。而在实际开发中,前后端常常是相互分离的,并且前后端的项目部署也常常不在一个服务器内或者在一个服务器的不同端口下。前端想要获取后端的数据,就必须发起请求,如果不错一些处理,就会受到浏览器同源策略的约束。后端可以收到请求并返回数据,但是前端无法收到数据。多种跨域方法跨域可以大概分为两种目的前后端分离时,前端为了获取后端数据而跨域为不同域下的前端页面通信而跨域为前后端分离而跨域Cross Origin Resource Share (CORS)CORS是一个跨域资源共享方案,为了解决跨域问题,通过增加一系列请求头和响应头,规范安全地进行跨站数据传输请求头主要包括请求头解释OriginOrigin头在跨域请求或预先请求中,标明发起跨域请求的源域名。Access-Control-Request-MethodAccess-Control-Request-Method头用于表明跨域请求使用的实际HTTP方法Access-Control-Request-HeadersAccess-Control-Request-Headers用于在预先请求时,告知服务器要发起的跨域请求中会携带的请求头信息响应头主要包括响应头解释Access-Control-Allow-OriginAccess-Control-Allow-Origin头中携带了服务器端验证后的允许的跨域请求域名,可以是一个具体的域名或是一个*(表示任意域名)。Access-Control-Expose-HeadersAccess-Control-Expose-Headers头用于允许返回给跨域请求的响应头列表,在列表中的响应头的内容,才可以被浏览器访问。Access-Control-Max-AgeAccess-Control-Max-Age用于告知浏览器可以将预先检查请求返回结果缓存的时间,在缓存有效期内,浏览器会使用缓存的预先检查结果判断是否发送跨域请求。Access-Control-Allow-MethodsAccess-Control-Allow-Methods用于告知浏览器可以在实际发送跨域请求时,可以支持的请求方法,可以是一个具体的方法列表或是一个*(表示任意方法)。如何使用客户端只需按规范设置请求头。服务端按规范识别并返回对应响应头,或者安装相应插件,修改相应框架配置文件等。具体视服务端所用的语言和框架而定SpringBoot 设置CORS例子一个spring boot项目中关于CORS配置的一段代码HttpServletResponse httpServletResponse = (HttpServletResponse) response; String temp = request.getHeader(“Origin”); httpServletResponse.setHeader(“Access-Control-Allow-Origin”, temp); // 允许的访问方法 httpServletResponse.setHeader(“Access-Control-Allow-Methods”, “POST, GET, PUT, OPTIONS, DELETE, PATCH”);// Access-Control-Max-Age 用于 CORS 相关配置的缓存 httpServletResponse.setHeader(“Access-Control-Max-Age”, “3600”); httpServletResponse.setHeader(“Access-Control-Allow-Headers”, “Origin, X-Requested-With, Content-Type, Accept,token”); httpServletResponse.setHeader(“Access-Control-Allow-Credentials”, “true”);JSONP 跨域jsonp的原理就是借助HTML中的<script>标签可以跨域引入资源。所以动态创建一个<srcipt>标签,src为目的接口 + get数据包 + 处理数据的函数名。后台收到GET请求后解析并返回函数名(数据)给前端,前端<script>标签动态执行处理函数观察下面代码前端代码<!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <title>Title</title></head><body><script> var script = document.createElement(‘script’); script.type = ’text/javascript’; // 传参并指定回调执行函数为getData script.src = ‘http://localhost:8080/users?username=xbc&callback=handleData’; document.body.appendChild(script); // 回调执行函数 function handleData(res) { data = JSON.stringify(res) console.log(data); }</script></body></html>后端代码(nodejs)var querystring = require(‘querystring’);var http = require(‘http’);var server = http.createServer();server.on(‘request’, function(req, res) { var params = querystring.parse(req.url.split(’?’)[1]); var fn = params.callback; // jsonp返回设置 res.writeHead(200, { ‘Content-Type’: ’text/javascript’ }); var data = { user: ‘xbc’, password: ‘123456’ } res.write(fn + ‘(’ + JSON.stringify(data) + ‘)’); res.end();});server.listen(‘8080’);console.log(‘Server is running at port 8080…’);在该例子中,前台收到的res是这样的前端页面是这样的注意JSONP既是利用了<srcipt>,那么就只能支持GET请求。其他请求无法实现nginx 反向代理实现跨域思路既然浏览器有同源策略限制,那我们把前端项目和前端要请求的api接口地址放在同源下不就可以了?再结合web服务器提供的反向代理,便可以在前端和后端都不做配置的情况下解决跨域问题。以nginx为例后端真实后台地址:http://xxx.xxx.xxx.xxx:8085 后台地址使用tomcat部署的spring boot项目 名为gsms_testnginx服务器地址: http://xxx.xxx.xxx.xxx:8082tomcat和nginx都是用docker架设的,做了端口转发nginx /etc/nginx/conf.d/default.conf配置代码如下server { listen 80; server_name localhost; #charset koi8-r; #access_log /var/log/nginx/host.access.log main; location / { root /usr/share/nginx/html/dist; # 前端项目路径 index index.html index.htm; autoindex on; autoindex_exact_size on; autoindex_localtime on; } location /gsms_test/ { proxy_pass 后端真实地址; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ .php$ { # proxy_pass http://127.0.0.1; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ .php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache’s document root # concurs with nginx’s one # #location ~ /.ht { # deny all; #}}不同域下页面通信而跨域window.name + iframe 跨域window.name是浏览器中一个窗口所共享的数据,在不同的页面(甚至不同域名)加载后依旧存在(如果没修改则值不会变化),并且可以支持非常长的 name 值(2MB)。比如 a域的某页面想获取b域某页面的数据,可以在b域中修改window.name值,a域切换到b域再切回来即可得到b域的window.name值。可是我们在开发中肯定不想页面切来切去,所以就要结合iframe来实现。示例 (以thinkjs实现)a 域代码如下<!DOCTYPE html><html><head><meta charset=“UTF-8”><title>A 域</title></head><body><h1>server A</h1><script type=“text/javascript”> function getData() { var iframe = document.getElementById(‘proxy’); iframe.onload = function () { var name = iframe.contentWindow.name; // 获取iframe窗口里的window.name值 console.log(name) } // 由于iframe信息传递也受同源策略限制,所以在window.name被B域修改后,将iframe转回A域下。以便获取iframe的window.name值 iframe.src = ‘http://127.0.0.1:8360/sub.html’ }</script><iframe id=“proxy” src=“http://127.0.0.1:8361/index.html” style=“width: 100%” onload=“getData()"> </iframe></body></html>b 域代码<!DOCTYPE html><html><head><meta charset=“UTF-8”><title>New ThinkJS Application</title></head><body> <h1>server 2</h1><script type=“text/javascript”> window.name = ‘user: xbc’;</script></body></html>注意由于受同源策略限制,父页面获取跨域的iframe页面的信息不全,所以要在iframe的window.name被B域修改后,转为A域下的任一页面(该一面不得修改window.name),在进行获取。代理页面 + iframe 实现跨域访问由于iframe与父页面相互访问也受同源策略限制,所以要借助一代理页面实现跨域。个人认为有些麻烦,若有兴趣请看前端如何用代理页面解决iframe跨域访问的问题?总结以上几种皆是本人用过或测试过的跨域方法,还有postMessage,WebSocket等跨域方法由于从未接触不做说明。在项目中具体使用那些方法还需具体考录各种问题情况方法只有GET请求JSONP对兼容性及浏览器版本无要求CROS对兼容性及浏览器版本有要求iframe 或 服务器反向代理本文参考经验 跨域方案CORS——跨域请求那些事儿前端如何用代理页面解决iframe跨域访问的问题?前端常见的跨域解决方案(全)CORS与服务器反向代理的优劣对比图解正向代理、反向代理、透明代理谢谢本文如有错误,欢迎留言私信讨论指出 ...

January 12, 2019 · 2 min · jiezi

Spring跨域配置

Spring跨域配置介绍跨站 HTTP 请求(Cross-site HTTP request)是指发起请求的资源所在域不同于该请求所指向资源所在的域的 HTTP 请求。比如说,域名A(http://domaina.example)的某 Web 应用程序中通过标签引入了域名B(http://domainb.foo)站点的某图片资源(http://domainb.foo/image.jpg),域名A的那 Web 应用就会导致浏览器发起一个跨站 HTTP 请求。在当今的 Web 开发中,使用跨站 HTTP 请求加载各类资源(包括CSS、图片、JavaScript 脚本以及其它类资源),已经成为了一种普遍且流行的方式。 出于安全考虑,浏览器会限制脚本中发起的跨站请求。比如,使用 XMLHttpRequest 对象发起 HTTP 请求就必须遵守同源策略(same-origin policy)。 具体而言,Web 应用程序能且只能使用 XMLHttpRequest 对象向其加载的源域名发起 HTTP 请求,而不能向任何其它域名发起请求。为了能开发出更强大、更丰富、更安全的Web应用程序,开发人员渴望着在不丢失安全的前提下,Web 应用技术能越来越强大、越来越丰富。比如,可以使用 XMLHttpRequest 发起跨站 HTTP 请求。(这段描述跨域不准确,跨域并非浏览器限制了发起跨站请求,而是跨站请求可以正常发起,但是返回结果被浏览器拦截了。最好的例子是crsf跨站攻击原理,请求是发送到了后端服务器无论是否跨域!注意:有些浏览器不允许从HTTPS的域跨域访问HTTP,比如Chrome和Firefox,这些浏览器在请求还未发出的时候就会拦截请求,这是一个特例。)普通参数跨域在response的头文件添加httpServletResponse.setHeader(“Access-Control-Allow-Origin”,"");httpServletResponse.setHeader(“Access-Control-Allow-Methods”,“POST”);httpServletResponse.setHeader(“Access-Control-Allow-Headers”,“Access-Control”);httpServletResponse.setHeader(“Allow”,“POST”);参数值描述Access-Control-Allow-Origin授权的源控制Access-Control-Allow-Credentialstrue / false是否允许用户发送和处理cookieAccess-Control-Allow-Methods[,]*允许请求的HTTP Method,多个用逗号分隔Access-Control-Allow-Headers[,]控制哪些header能发送真正的请求,多个用逗号分隔Access-Control-Max-Age秒授权的时间,单位为秒。有效期内,不会重复发送预检请求带headr请求跨域这样客户端需要发起OPTIONS请求, 可以说是一个【预请求】,用于探测后续真正需要发起的跨域 POST 请求对于服务器来说是否是安全可接受的,因为跨域提交数据对于服务器来说可能存在很大的安全问题。因为Springmvc模式是关闭OPTIONS请求的,所以需要开启<servlet> <servlet-name>application</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>dispatchOptionsRequest</param-name> <param-value>true</param-value> </init-param> <load-on-startup>1</load-on-startup></servlet>开启CORSSpringMVC从4.2版本开始增加了对CORS的支持。在springMVC 中增加CORS支持非常简单,可以配置全局的规则,也可以使用@CrossOrigin注解进行细粒度的配置。使用@CrossOrigin注解先通过源码看看该注解支持的属性在Controller上使用@CrossOrigin注解// 指定当前的AccountController中所有的方法可以处理所有域上的请求@CrossOrigin(origins = {“http://domain1.com”, “http://domain2.com”}, maxAge = 72000L)@RestController@RequestMapping("/account")public class AccountController { @RequestMapping("/{id}") public Account retrieve(@PathVariable Long id) { // … } @RequestMapping(method = RequestMethod.DELETE, path = “/{id}”) public void remove(@PathVariable Long id) { // … }}在方法上使用@CrossOrigin注解@CrossOrigin(maxAge = 3600L)@RestController@RequestMapping("/account")public class AccountController { @CrossOrigin(origins = {“http://domain1.com”, “http://domain2.com”}) @RequestMapping("/{id}") public Account retrieve(@PathVariable Long id) { // … } @RequestMapping(method = RequestMethod.DELETE, path = “/{id}”) public void remove(@PathVariable Long id) { // … }}CORS全局配置除了细粒度基于注解的配置,可以定义全局CORS的配置。这类似于使用过滤器,但可以在Spring MVC中声明,并结合细粒度@CrossOrigin配置。默认情况下所有的域名和GET、HEAD和POST方法都是允许的。基于XML的配置<mvc:cors> <mvc:mapping path="/api/" allowed-origins=“http://domain1.com, http://domain2.com” allowed-methods=“GET, POST, PUT, HEAD, PATCH, DELETE, OPTIONS, TRACE” allowed-headers=“header1, header2, header3” exposed-headers=“header1, header2” allow-credentials=“false” max-age=“72000” /> <mvc:mapping path="/resources/" allowed-origins=“http://domain3.com” /></mvc:cors>基于代码的配置这个方法同样适用于SpringBoot。@Configurationpublic class WebConfig extends WebMvcConfigurerAdapter { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/") .allowedOrigins(“http://domain1.com”) .allowedOrigins(“http://domain2.com”) .allowedMethods(“GET”, “POST”, “PUT”, “HEAD”, “PATCH”, “DELETE”, “OPTIONS”, “TRACE”); .allowedHeaders(“header1”, “header2”, “header3”) .exposedHeaders(“header1”, “header2”) .allowCredentials(false) .maxAge(72000L); registry.addMapping("/resources/") .allowedOrigins(“http://domain3.com”); }}基于过滤器的配置import org.springframework.web.cors.CorsConfiguration;import org.springframework.web.cors.UrlBasedCorsConfigurationSource;@Beanpublic FilterRegistrationBean corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.addAllowedOrigin(“http://domain1.com”); config.addAllowedOrigin(“http://domain2.com”); config.addAllowedHeader(""); config.addAllowedMethod(“GET, POST, PUT, HEAD, PATCH, DELETE, OPTIONS, TRACE”); config.setAllowCredentials(true); config.setMaxAge(72000L); // CORS 配置对所有接口都有效 source.registerCorsConfiguration("/**", config); FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source)); bean.setOrder(0); return bean;} ...

January 9, 2019 · 2 min · jiezi

一次弄懂跨域问题

前端请求最常提到跨域问题,但是,很多开发者一直在跨域,还是搞不清楚什么是跨域?为什么要跨域?跨域方法有哪些?在我的深入研究后,写下此文,希望对你们有所帮助。同源策略1.什么是同源策略?同源策略 (Same-Origin Policy) 最早由 Netscape 公司提出, 所谓同源就是要求, 域名, 协议, 端口相同. 非同源的脚本不能访问或者操作其他域的页面对象(如DOM等). 作为著名的安全策略, 虽然它只是一个规范, 并不强制要求, 但现在所有支持 javaScript 的浏览器都会使用这个策略. 以至于该策略成为浏览器最核心最基本的安全功能, 如果缺少了同源策略, web的安全将无从谈起.并且,浏览器不是阻止请求的发送,而是对请求的拦截。同源策略要求三同, 即: 同域, 同协议, 同端口.同域即host相同, 顶级域名, 一级域名, 二级域名, 三级域名等必须相同, 且域名不能与 ip 对应;同协议要求, http与https协议必须保持一致;同端口要求, 端口号必须相同.注:(IE有些例外, 它仅仅只是验证主机名以及访问协议,而忽略了端口号.)2.同源策略带来的问题?同源策略下的web世界, 域的壁垒高筑, 从而保证各个网页相互独立, 互相之间不能直接访问, iframe, ajax 均受其限制, 而script标签不受此限制.iframe限制可以访问同域资源, 可读写;访问跨域页面时, 只读.Ajax限制Ajax 的限制比 iframe 限制更严.同域资源可读写;跨域请求会直接被浏览器拦截.(chrome下跨域请求不会发起, 其他浏览器一般是可发送跨域请求, 但响应被浏览器拦截)Script限制script并无跨域限制, 这是因为script标签引入的文件不能够被客户端的 js 获取到, 不会影响到原页面的安全, 因此script标签引入的文件没必要遵循浏览器的同源策略. 相反, ajax 加载的文件内容可被客户端 js 获取到, 引入的文件内容可能会泄漏或者影响原页面安全, 故, ajax必须遵循同源策略.跨域办法使用代理虽然ajax和iframe受同源策略限制, 但服务器端代码请求, 却不受此限制, 我们可以基于此去伪造一个同源请求, 实现跨域的访问. 如下便是实现思路:请求同域下的web服务器;web服务器像代理一样去请求真正的第三方服务器;(服务器不存在跨域问题)代理拿到数据过后, 直接返回给客户端ajax.JSONPscript标签并不受同源策略约束, 基于script 标签可做 jsonp 形式的访问可以通过第三方服务器生成动态的js代码来回调本地的js方法,而方法中的参数则由第三方服务器在后台获取,并以JSON的形式填充到JS方法当中.由于使用script标签的src属性,因此只支持get方法<scriptsrc=“https://www.targetDomain.com/jsonp?callback=callbackName"></script>postMessageES5新增的 postMessage() 方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递.语法: postMessage(data,origin)data: 要传递的数据,html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候建议使用JSON.stringify()方法对对象参数序列化,在低版本IE中引用json2.js可以实现类似效果.origin:字符串参数,指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,所以可以不写,这个参数是为了安全考虑,postMessage()方法只会将message传递给指定窗口,当然如果愿意也可以建参数设置为”*",这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。父页面发送消息:window.frames[0].postMessage(‘message’, origin)iframe接受消息:window.addEventListener(‘message’,function(e){ if(e.source!=window.parent) return;//若消息源不是父页面则退出 //TODO …});其中 e 对象有三个重要的属性:data, 表示父页面传递过来的messagesource, 表示发送消息的窗口对象origin, 表示发送消息窗口的源(协议+主机+端口号)CORS 跨域访问HTML5带来了一种新的跨域请求的方式 — CORS, 即 Cross-origin resource sharing. 它更加安全, 上述的 JSONP, postMessage 等, 资源本身没有能力保证自己不被滥用. CORS的目标是保护资源只被可信的访问源以正确的方式访问.(目前, 主流的浏览器都支持此协议)CORS 的请求分两种,这也是浏览器为了安全做的一些处理,不同情况下浏览器执行的操作也是不一样的,主要分为两种请求,当然这一切我们是不需要做额外处理的,浏览器会自动处理的。简单请求(simple request)只要同时满足以下两大条件,就属于简单请求。条件:1) 请求方法是以下三种方法中的一个:HEADGETPOST2)HTTP的头信息不超出以下几种字段:AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain过程:对于简单的跨域请求,浏览器会自动在请求的头信息加上 Origin 字段,表示本次请求来自哪个源(协议 + 域名 + 端口),服务端会获取到这个值,然后判断是否同意这次请求并返回。// 请求GET /cors HTTP/1.1Origin: http://api.qiutc.meHost: api.alice.comAccept-Language: en-USConnection: keep-aliveUser-Agent: Mozilla/5.0…1.服务端允许// 返回Access-Control-Allow-Origin: http://api.qiutc.meAccess-Control-Allow-Credentials: trueAccess-Control-Expose-Headers: InfoContent-Type: text/html; charset=utf-8这三个带有 Access-Control 开头的字段分别表示:Access-Control-Allow-Origin必须。它的值是请求时Origin字段的值或者 ,表示接受任意域名的请求。Access-Control-Allow-Credentials;可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。再需要发送cookie的时候还需要注意要在AJAX请求中打开withCredentials属性:var xhr = new XMLHttpRequest(); xhr.withCredentials = true;需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且原网页代码中的document.cookie也无法读取服务器域名下的Cookie。Access-Control-Expose-Headers可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader(‘Info’)可以返回Info字段的值。2.服务端拒绝当然我们为了防止接口被乱调用,需要限制源,对于不允许的源,服务端还是会返回一个正常的HTTP回应,但是不会带上 Access-Control-Allow-Origin 字段,浏览器发现这个跨域请求的返回头信息没有该字段,就会抛出一个错误,会被 XMLHttpRequest 的 onerror 回调捕获到。这种错误无法通过 HTTP 状态码判断,因为回应的状态码有可能是2002.非简单请求条件:除了简单请求以外的CORS请求。非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。过程1)预检请求非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。预检请求的发送请求:OPTIONS /cors HTTP/1.1Origin: http://api.qiutc.meAccess-Control-Request-Method: PUTAccess-Control-Request-Headers: X-Custom-HeaderHost: api.qiutc.comAccept-Language: en-USConnection: keep-aliveUser-Agent: Mozilla/5.0…“预检”请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。除了Origin字段,”预检”请求的头信息包括两个特殊字段。Access-Control-Request-Method该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。Access-Control-Request-Headers该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。预检请求的返回:HTTP/1.1 200 OKDate: Mon, 01 Dec 2008 01:15:39 GMTServer: Apache/2.0.61 (Unix)Access-Control-Allow-Origin: http://api.qiutc.meAccess-Control-Allow-Methods: GET, POST, PUTAccess-Control-Allow-Headers: X-Custom-HeaderContent-Type: text/html; charset=utf-8Content-Encoding: gzipContent-Length: 0Keep-Alive: timeout=2, max=100Connection: Keep-AliveContent-Type: text/plainAccess-Control-Allow-Methods 必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次”预检”请求。Access-Control-Allow-Headers 如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在”预检”中请求的字段。Access-Control-Max-Age 该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。2)浏览器的正常请求和回应一旦服务器通过了”预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。flash URLLoderflash有自己的一套安全策略, 服务器可以通过crossdomain.xml文件来声明能被哪些域的SWF文件访问, SWF也可以通过API来确定自身能被哪些域的SWF加载. 当跨域访问资源时, 例如从域 a.com 请求域 b.com上的数据, 我们可以借助flash来发送HTTP请求.首先, 修改域 b.com上的 crossdomain.xml(一般存放在根目录, 如果没有需要手动创建) , 把 a.com 加入到白名单;<?xml version=“1.0”?><cross-domain-policy><site-control permitted-cross-domain-policies=“by-content-type”/><allow-access-from domain=“a.com” /></cross-domain-policy>其次, 通过Flash URLLoader发送HTTP请求, 拿到请求后并返回;最后, 通过Flash API把响应结果传递给JavaScript.Flash URLLoader是一种很普遍的跨域解决方案,不过需要支持iOS的话,这个方案就不可行了.WebSocket在WebSocket出现之前, 很多网站为了实现实时推送技术, 通常采用的方案是轮询(Polling)和Comet技术, Comet又可细分为两种实现方式, 一种是长轮询机制, 一种称为流技术, 这两种方式实际上是对轮询技术的改进, 这些方案带来很明显的缺点, 需要由浏览器对服务器发出HTTP request, 大量消耗服务器带宽和资源. 面对这种状况, HTML5定义了WebSocket协议, 能更好的节省服务器资源和带宽并实现真正意义上的实时推送.WebSocket 本质上是一个基于TCP的协议, 它的目标是在一个单独的持久链接上提供全双工(full-duplex), 双向通信, 以基于事件的方式, 赋予浏览器实时通信能力. 既然是双向通信, 就意味着服务器端和客户端可以同时发送并响应请求, 而不再像HTTP的请求和响应. (同源策略对 web sockets 不适用)原理: 为了建立一个WebSocket连接,客户端浏览器首先要向服务器发起一个HTTP请求, 这个请求和通常的HTTP请求不同, 包含了一些附加头信息, 其中附加头信息”Upgrade: WebSocket”表明这是一个申请协议升级的HTTP请求, 服务器端解析这些附加的头信息然后产生应答信息返回给客户端, 客户端和服务器端的WebSocket连接就建立起来了, 双方就可以通过这个连接通道自由的传递信息, 并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接.参考:跨域资源共享 CORS 详解跨域访问和防盗链基本原理-WEB前端-伯乐在线html5 postMessage解决跨域、跨窗口消息传递-Samaritans-博客园 同源策略详解及绕过(Part1) ...

January 1, 2019 · 2 min · jiezi

推荐系统之信息茧房问题

什么是信息茧房信息茧房其实是现在社会一个很可怕的现象,从字面意思来看的话其实比喻的是信息被虫茧一般封锁住。这个问题反映了现在随着个性化推荐的普及衍射的一个社会问题。平时在浏览新闻或者淘宝的时候,平台会自动根据用户的浏览记录获取用户的偏好,然后推送感兴趣的文章。久而久之,比如用户A是个体育迷,那么A获取的信息大多是跟体育相关的,很难获取音乐或者军事等其它相关的资讯,因为平台追求点击率,会一直推送A感兴趣的内容以获取高广告浏览量。时间长了,因为信息茧房的作用,因为信息获取单一,A的社交圈可能也会变的狭小。如果整个社会陷入了个性化推荐系统的信息茧房效应,将是病态的。所以,真正的个性化推荐绝对不等于只推荐历史感兴趣的内容,这样其实不是一个长期可持续的推荐系统,如果陷入了信息茧房,一定会有用户觉得审美疲劳。那么如何破解信息茧房,因为从推荐模型角度分析,一旦获取了用户的画像,就很难跳出用户习惯的逻辑,比如昨天买个手机,第二天还推荐手机,这个时候可能比较好的一种方法是跨域推荐(cross-domain recommendation)。跨域推荐的概念跨域推荐做的事情就是跳出推荐的信息茧房。不是一个新概念了,我上研究生的时候学校就有实验室做相关的研究,今天主要讲下思路。具体大家想了解的话可以看下这个Paper: 《Cross-Domain Recommendation: An Embedding and Mapping Approach》有几个关键词我觉得可以充分体现跨域推荐的精髓:“diversity” - “novelty” - “serendipity”如果我们做一个推荐系统,说是“individuality”,其实我会觉得很normal,不够高级,现在几乎所有推荐系统都有个性化推荐,但是如果一个推荐系统标榜自己是“novelty”,那我就觉得很有意思了。下面聊聊怎么实现novelty。第一步:确定什么是target & source这里以新闻推荐为例,如果一用户A,经常浏览同一个类型的新闻,比如体育新闻,如何找到A喜欢的其他类别新闻呢?这其实是一个user overlap的场景,推荐系统的主体user不变,有个source源是体育新闻,要找到target是体育以外user感兴趣的文章。这就建立了跨域推荐中的target和source关系。第二步:确定推荐level跨域推荐有多种level,要确定跨域的种类,大体可以分以下三种:其实跨域推荐确定了source和target后只要确定跨域的幅度即可。Attribute level:挖掘target间的相似属性,推荐同一类别的target。比如一个用户很喜欢买红色、大尺寸的诺基亚手机,attribute level推荐是要在推荐物属性层面做跨域,可以试着给用户推荐黑色、小尺寸的其它手机,这样的跨属性的相同物种推荐会在一定程度上给用户新鲜感Type level:挖掘target间的相似属性,然后推荐相同大品类下不同小品类的物品。比如用户喜欢红色、大尺寸的诺基亚手机,手机和电脑都属于电子产品,可以推荐红色、大尺寸的电脑给用户Item level:挖掘target间的相似属性,推荐不同大品类的物品。比如用户喜欢红色、大尺寸的诺基亚手机,直接推荐红色大尺寸的马桶以上3个跨域level由轻到重,大家可以根据自己的需求选用。其实关键点是如何挖掘物品的属性,因为无论是电脑、手机、马桶,他们都有共通的属性,比如大小、颜色、材质等,下面就介绍如何挖掘这些属性。第三步:挖掘target间的属性既然跨域推荐的关键是能挖掘出target间共有的属性,那么有什么办法可以做到这一点呢。首先要根据业务属性人工挖掘出一些隐性特征,比如电商平台可以挖掘出颜色、材质、价格、使用频率等隐性特征,然后可以通过矩阵分解的方式获取具体每个特征的权重(下图中矩阵A和B之间的矩阵)。总结信息茧房效应是因为个性化推荐系统推荐信息的不平衡性,导致用户长期只能浏览限制领域的信息,可以在推荐系统中加入跨域推荐的逻辑来规避信息茧房的影响,具体流程包含确定推荐逻辑中的source和target,确定跨域的粒度,通过矩阵分解找出隐含的共性属性。参考:https://recsys.acm.org/wp-con…本文作者:傲海阅读原文本文为云栖社区原创内容,未经允许不得转载。

December 27, 2018 · 1 min · jiezi

http跨域详解

跨域为什么会有跨域??由于浏览器的同源策略限制,浏览器会拒绝跨域请求,那么什么是同源呢?如果两个页面的协议,端口,和域名都相同,则两个页面具有相同的源。如果3者有一个不同,则为跨域。域的更改页面可能会因某些限制而改变他的源。脚本可以将document.domain的值设置为其当前域或者当前域的超级域。如果将其设置为其当前域的超级域,则较短的域将用于后续源检查。假设http://store.company.com/dir/…:document.domain = “company.com"这条语句执行之后,页面将会成功地通过对http://company.com/dir/page.h…(假设http://company.com/dir/page.h…)。这里必须两个页面都设置document.domain才能通过同源策略。这是因为设置document.domain都会导致端口口号被重写为null。(即使document.domain= document.domain).只有子域和父域都设置document.domain。才能确保端口号都为null。跨域网络访问同源策略控制了不同源之间的交互,例如在使用xmlhttpRequest或img标签时候则会受到同源策略的约束,这些交互通常分为3类1 通常允许跨域写操作(cross-origin whites) 。例如重定向表单提交等2 通常允许跨域资源嵌入(cross-orgin embedding). 例如img <script> <iframe>3 通常不允许跨域读操作(cross-origin reads)。但常可以通过内嵌资源来巧妙的进行读取访问。例如可以读取嵌入图片的高度和宽度和jsonp内嵌脚本的方式。如何允许跨域访问使用cors允许跨域访问。(cross-origin resource sharing)浏览器将cors请求分为两类。简单请求(simple request)和非简单请求(not so simple request)只要同时满足一下两大条件,就输入简单请求1)请求方法是以下三种方法之一 head get post2) http的头信息不超出以下几种字段:accept accept-language content-language last-event-id content-type(只限于三个值application/x-www-form-urlencoded multipart/form-data text/plain)凡是不同时满足上面两个条件。就属于非简单请求。浏览器对这两种请求的处理,是不一样的。简单请求浏览器发现这个跨域ajax请求是简单请求,就自动在头信息之中,添加一个origin。如果origin指定的源,不在许可范围内,服务器会返回一个正常的http回应。浏览器发现,这个回应的头没有包含access-control-allow-origin字段。就知道出错了。从而抛出一个错误,被xmlhttprequest onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为http回应的状态码有可能是200.。Access-Control-Allow-Origin: http://api.bob.com 该字段必须,它的值要么是请求的origin的值,要么是*.表示接受任意域名的请求。Access-Control-Allow-Credentials: true 字段可选,表示是否发送cookie。默认情况下,cookie不包括在cors请求之中设为true,表示服务器明确许可,coookie可以包含在请求中,一起发给服务器。这个值也只能为true,如果服务器不要浏览器发送cookie,删除该字段即可。另外ajax请求也必须打开withCredentials属性var xhr = new XMLHttpReqeust();xhr.withCredentials = true;否则,即使服务器同意发送cookie。浏览器也不会发送。但是如果省略withCredentials设置,有的浏览器还是会一起发送cookie。这时可以显式关闭withCredentials = false;** 需要注意,如果要发送cookie。access-control-allow-origin就不能设为星号。必须指定明确一致的域名。同时cookie依然遵循同源政策,只有用服务器域名设置的cookie才会上传,其他域名的cookie并不会上传,且原网页代码中的document.cookie也无法读取服务器域名下的cookie.**Access-Control-Expose-Headers: FooBar 该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader(‘FooBar’)可以返回FooBar字段的值。非简单请求非简单请求时那种对服务器有特殊要求的请求,比如请求方式是put或delete。或者content-type字段的类型是application/json非简单请求的cors请求,会在正式通信之前,增加一次http查询请求,称为预检请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,已经可以使用那些http动词和头信息。只有得到肯定答复,浏览器才会发出正式的xmlhttprequest请求,否则就报错。OPTIONS /cors HTTP/1.1Origin: http://api.bob.comAccess-Control-Request-Method: PUTAccess-Control-Request-Headers: X-Custom-HeaderHost: api.alice.comAccept-Language: en-USConnection: keep-aliveUser-Agent: Mozilla/5.0…预检请求用的请求方式是optioons,表示这个请求时用来询问的。头信息里面,关键字段是origin,表示请求来着那个源。除了origin字段,预检请求的头信息包括两个特殊字段。access-control-request-method 该字段是必须的,用来列出浏览器cors请求会用到哪些http方法。access-control-request-headers。 该字段是一个逗号分隔的字符串,指定浏览器cors请求会额外发送的头信息字段。预检请求的回应HTTP/1.1 200 OKDate: Mon, 01 Dec 2008 01:15:39 GMTServer: Apache/2.0.61 (Unix)Access-Control-Allow-Origin: http://api.bob.comAccess-Control-Allow-Methods: GET, POST, PUTAccess-Control-Allow-Headers: X-Custom-HeaderContent-Type: text/html; charset=utf-8Content-Encoding: gzipContent-Length: 0Keep-Alive: timeout=2, max=100Connection: Keep-AliveContent-Type: text/plain上面回应中,关键字段是access-control-allow-originaccess-control-allow-methodsaccess-control-allow-headers通常返回200 后者204.204与200的不同就是。204不需要携带多余的响应体,节省流量如果浏览器否定了预检请求。会返回一个正常的http回应,但是没有任何cors相关的头信息字段。服务器回应的其他cors相关字段如下Access-Control-Allow-Methods: GET, POST, PUTAccess-Control-Allow-Headers: X-Custom-HeaderAccess-Control-Allow-Credentials: trueAccess-Control-Max-Age: 17280001)Access-Control-Allow-Methods该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。(2)Access-Control-Allow-Headers如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。(3)Access-Control-Allow-Credentials该字段与简单请求时的含义相同。(4)Access-Control-Max-Age该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。一旦服务器通过了预检请求,以后每次浏览器正常的cors请求,就都跟简单请求一样,会有一个origin头信息字段,服务器的回应,也都会有一个access-control-allow-origin头信息字段。与jsonp的比较cors与jsonp的使用目的相同,但是比jsonp更强大jsonp只支持get 请求。cors支持所有类型的http请求。jsonp的优势在于支持老式浏览器以及可以不支持cors的网站请求数据。 ...

December 25, 2018 · 1 min · jiezi

CORS 理解(不要那么多术语)

摘要谈到跨域,不论前端还是后端,多少有点谈虎色变,面试中也常会问到这些问题,浏览器和服务器端到底怎么做才能跨域,他们都做了什么?同源 vs 跨域同源,字面意义是相同的源头,即同一个web服务器(比如tomcat启动的一个实例),好比一个家庭;跨域就是从一个web服务器向另一个服务器发送或获取数据,好比去邻居家拿东西。之所以要做跨域限制,是为了安全,你不能从邻居家直接拿吧,总得问过人家。那么怎么来判断同源?就看一个web服务器的根本三要素:协议、web服务器域名、端口号。比如:当前页面的地址是http://myhost.com:8080/index,源就是http://myhost.com:8080,当新请求的协议、域名、端口号与之完全一致即是同源,否则是跨域。对于下面发送的请求,浏览器的判决如下:http://www.myhost.com:8080/users 跨域 -> 域名必须完全一致https://myhost.com:8080/users 跨域 -> 协议必须一致https://myhost.com:9000/users 跨域 -> 端口必须一致https://myhost.com/users 跨域 -> 端口必须一致(没有端口也是不一致)https://myhost.com:8080/profile/users 同源跨域时的动作浏览器端假设我们点击一个按钮去获取数据,获取数据的请求准备从浏览器发出,这时浏览器先会检测这条请求是同源还是跨域,也就是与按钮所在页面的地址是同源还是跨域,如果是同源,好说,直接发送出去;如果是跨域的请求,那就得hold住先,浏览器会在请求的http header中加上一个Origin字段,标明这个请求是从哪里发出来的,例如: Origin:http://neighbour.com:9000,这样服务器端好辨识是自己家人来取东西,还是隔壁老王来借东西了。那些做检测、加header字段等事情全是浏览器做,对于前端开发者来说,什么事都不用干,ajax请求平时怎么发送,跨域时还怎么发送。可见,跨域对于前端没影响,关键在于服务器端。服务器端每个web服务器就像一个家庭。好邻居要吃螃蟹来借点醋,这没问题;坏邻居家有醋要来借点螃蟹,这不干。服务器收到请求会给与响应,响应的header里写明跨域的配置信息,告诉浏览器,它允许哪些域名发来的请求访问,哪些method可以执行。浏览器收到响应后自动判断能不能真正执行请求。假设服务器域名是http://rich.com:8080,它收到了一个从http://neighbour.com:9000发来的请求http://rich.com:9000/borrow-vinegar,服务器响应它,在响应中写明本服务器支持哪些域名可以访问,哪些method可以执行,浏览器收到后做匹配,再判定。那么,服务器有哪些判定的规则呢?是否允许跨域的判定一个支持CORS的web服务器,有如下的判定字段,他们会在响应的header中写明Access-Control-Allow-Origin:允许跨域的Origin列表Access-Control-Allow-Methods:允许跨域的方法列表Access-Control-Allow-Headers:允许跨域的Header列表Access-Control-Expose-Headers:允许暴露给JavaScript代码的Header列表Access-Control-Max-Age:最大的浏览器缓存时间,单位为s其中Access-Control-Allow-Origin(访问控制之允许的源),在响应的http header中必须有的,表示允许访问本服务器的源头Origin(域名),可以是特定的域名列表,用逗号分隔,也可以是通配符 *,表示支持任意域名的访问。除了限定源头Origin,还会限制请求的方法Method,Header。如,如果服务器设定Access-Control-Allow-Methods:GET,那么跨域的POST请求无法在这个服务器执行。总流程页面发送请求浏览器根据同源策略做出判定,如果是同源请求,直接发送出去;如果是跨域请求,在HTTP HEADER加上Origin字段,或是先发送一次预检请求(preflight)。服务器接收请求,根据自身跨域的配置(如允许哪些域名,什么样的Method访问),返回文件头。若未配置过任何允许跨域,则文件头里不包含Access-Control-Allow-origin字段,若配置过域名,则返回Access-Control-Allow-origin+ 对应配置规则里的域名的方式。浏览器接收到响应,根据响应头里的`Access-Control-Allow-origin字段做匹配,如果没有这个字段,说明不匹配;如果有,将字段内容和当前域名做比对。如匹配,则可以发送请求。跨域的请求形式跨域的请求分两种,一种是简单请求,一种是非简单请求(废话)。简单请求,方法仅限于 HEAD,GET或POST,且Header的字段不超过以下字段:HeadeAcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain (没有application/json, 说明如果发送JSON格式的body请求数据是一个非简单请求)非简单请求就是其他请求简单请求浏览器会直接在请求的Header加上Origin字段再发送;非简单请求浏览器则会先发送一次预检请求,根据预检请求的结果,决定是否正式发送请求。详情可以访问阮一峰的日志:跨域资源共享 CORS 详解

December 22, 2018 · 1 min · jiezi

VUE跨域问题

vue-cli项目中,解决跨域问题。在config > index.js 文件中的proxyTable里边添加’/api’配置proxyTable: { ‘/api’: { // 跨域域名 target: ‘http://www.xxxxx.cn’, // 是否跨域 changeOrigin: true, pathRewrite: { ‘^/api’: ’’ } }},在vue组件中使用methods: { // 加载数据 getUserInfo () { this.$axios.get(‘api/mall/servlet/json?funcNo=3040’) // this.$axios.get(‘http://www.xxxxx.cn/mall/servlet/json?funcNo=3040') .then(this.handleGetUserInfoSucc) .catch(error => console.log(error)) }} 需要重新运行项目,不然会报错重新运行项目后本地开发就不会出现跨域问题了。当项目打包,放到服务器上,会报错Failed to load resource: the server responded with a status of 404 (Not Found)开发的时候用的dev_server只针对开发用的,打包后dev_server这块配置的请求代理已经无效。这时直接将域名放到get中,打包放到服务器methods: { // 加载数据 getUserInfo () { // this.$axios.get(‘api/mall/servlet/json?funcNo=3040’) this.$axios.get(‘http://www.xxxxx.cn/mall/servlet/json?funcNo=3040') .then(this.handleGetUserInfoSucc) .catch(error => console.log(error)) }}

December 18, 2018 · 1 min · jiezi