本文有配套视频:
https://www.bilibili.com/video/av58096866/?p=8
一、为什么会出现跨域的问题
跨域问题由来已久,主要是来源于浏览器的”同源策略”。
何为同源?只有当协议、端口、和域名都相同的页面,则两个页面具有相同的源。只要网站的 协议名protocol、 主机host、 端口号port 这三个中的任意一个不同,网站间的数据请求与传输便构成了跨域调用,会受到同源策略的限制。
同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。浏览器的同源策略,出于防范跨站脚本的攻击,禁止客户端脚本(如JavaScript)对不同域的服务进行跨站调用(通常指使用XMLHttpRequest请求)。
发生跨域的三个必要条件:
浏览器限制: 即浏览器对跨域行为进行检测和阻止;
触发跨域的三要素之一: 即 协议,域名和端口三个条件满足其一;
发起的是xhr请求: 即发起的是XMLHttpRequest类型的请求;
所以说我们在web中,我们无法去获取跨域的请求,常见的就是无法通过js获取接口。
这里要说下我的以前使用的经验:在同源系统下,前端js去调用后端接口,然后后端C#去调取跨域接口,这是我以前采用的办法,但是前后端分离,这个办法肯定就是不行了,因为那时候的MVC仅仅是页面上的前和后,还是一个项目,现在却是不同域名或端口的两个项目。
但是只要我们合理使用同源策略,就可以达到跨域访问的目的。
二、JsonP
首先需要建立了一个前端项目,用 IIS 代理一下,用来模拟前后端分离后的前端访问部分,具体如下步骤:
1、模拟前端访问页面
在 wwwroot 文件夹下,新建一个 CorsPost.html 静态页面,使用Jquery来发送请求。
设计了2种跨域方法,一个是 JSONP 的,一个是 CORS 的:
<!DOCTYPE html>
<html>
<head> <meta charset="utf-8"> <title>Blog.Core</title> <script src="https://cdn.bootcss.com/jquery/1.10.2/jquery.min.js"></script> <style> div { margin: 10px; word-wrap: break-word; }
</style> <script> $(document).ready(function () { $("#jsonp").click(function () { $.getJSON("http://localhost:8081/api/Login/jsonp?callBack=?", function (data) { $("#data-jsonp").html("数据: " + data.value); }); }); $("#cors").click(function () { $.get("http://localhost:8081/api/Login/Token", function (data, status) { console.log(data); $("#status-cors").html("状态: " + status); $("#data-cors").html("数据: " + data? data.token:"失败"); }); }); $("#cors-post").click(function () { let postdata = { "bID": 10, "bsubmitter": "222", "btitle": "33333", "bcategory": "4444", "bcontent": "5555", "btraffic": 0, "bcommentNum": 0, "bUpdateTime": "2018-11-08T02:36:26.557Z", "bCreateTime": "2018-11-08T02:36:26.557Z", "bRemark": "string" }; $.ajax({ type: 'post', url: 'http://localhost:8081/api/Values', contentType: 'application/json', data: JSON.stringify(postdata), success: function (data, status) { console.log(data); $("#status-cors-post").html("状态: " + status); $("#data-cors-post").html("数据: " + JSON.stringify(data)); } }); }); });
</script>
</head>
<body> <h3>通过JsonP实现跨域请求</h3> <button id="jsonp">发送一个 GET </button> <div id="status-jsonp"></div> <div id="data-jsonp"></div> <hr /> <h3>通过CORS实现跨域请求,另需要在服务器段配置CORE</h3> <button id="cors">发送一个 GET </button> <div id="status-cors"></div> <div id="data-cors"></div> <hr /> <button id="cors-post">发送一个 POST </button> <div id="status-cors-post"></div> <div id="data-cors-post"></div> <hr />
</body>
</html>
注意:这里一定要注意jsonp的前端页面请求写法,要求很严谨
2、请求页面部署
1、其实只需要当前Blog.Core 项目配置了静态文件中间件,直接访问就可以,
比如我的在线地址:http://xxxxx:8081/corspost.html,但是这样起不到跨域的目的,因为这样前台和后台,还是公用的一个 8081 端口,方法不推荐。
2、单独部署:将这个页面部署到自己的IIS中,拷贝到文件里,直接在iis添加该文件,访问刚刚的Html文件目录就行,推荐。
3、设计后台接口
在我们的项目 LoginController 中,设计Jsonp接口,Core调用的接口我们已经有了,就是之前获取Token的接口GetJWTStr
/// <summary> /// 获取JWT的方法4:给 JSONP 测试 /// </summary> /// <param name="callBack"></param> /// <param name="id"></param> /// <param name="sub"></param> /// <param name="expiresSliding"></param> /// <param name="expiresAbsoulute"></param> /// <returns></returns> [HttpGet] [Route("jsonp")] public void Getjsonp(string callBack, long id = 1, string sub = "Admin", int expiresSliding = 30, int expiresAbsoulute = 30) { TokenModelJwt tokenModel = new TokenModelJwt { Uid = id, Role = sub }; string jwtStr = JwtHelper.IssueJwt(tokenModel); string response = string.Format("\"value\":\"{0}\"", jwtStr); string call = callBack + "({" + response + "})"; Response.WriteAsync(call); }
注意:这里一定要注意jsonp的接口写法,要求很严谨
4、测试
点击 “通过JsonP实现跨域请求”按钮,发现已经有数据了,证明Jsonp跨域已经成功,你可以换成自己的域名试一试,但是Cors的还不行
现在咱们就说说这种JSONP跨域的优劣有哪些:
优势:
1、操作很简单;
2、支持老式浏览器;
劣势:
1、这种方式只能发生get请求;
2、确定jsonp的请求是否失败并不容易,大多数框架的实现都是结合超时时间来判定;
3、不太安全,可能也会受到攻击
从上边咱们可以看出来,虽然JSONP操作起来很简单,几乎和我们的 Ajax 请求没有什么区别,但是弊端也特别大,目前市场上并没有很好的流通起来,那有没有更通用的,更安全的跨域方案呢,没错,就是今天的重头戏 —— CORS。
三、CORS
这个方法是目前我个人感觉,最简单,最安全的方法,详细步骤如下:
1、前端ajax调用
前端的代码在jsonp的时候已经写好,请往上看第二大节的第一步骤,
后端接口也是一个很简单的 /api/Login/Token 接口
剩下的就是配置跨域了,很简单!
2、配置 CORS 跨域
在 startup.cs 启动文件的 ConfigureServices 中添加
services.AddCors(c => { // 配置策略 c.AddPolicy("LimitRequests", policy => { // 支持多个域名端口,注意端口号后不要带/斜杆:比如localhost:8000/,是错的 // http://127.0.0.1:1818 和 http://localhost:1818 是不一样的,尽量写两个 policy .WithOrigins("http://127.0.0.1:1818", "http://localhost:8080", "http://localhost:8021", "http://localhost:8081", "http://localhost:1818") .AllowAnyHeader()//允许任意头 .AllowAnyMethod();//允许任意方法 }); });
基本注释都有,大家都能看的懂,这里说一下,有三个小点需要了解:
注意:
1、在定义策略 LimitRequests 的时候,源域名应该是客户端 vue 项目的请求的端口域名,不是当前API的域名端口。
2、上边我们是在 configureService 里配置的策略,其实我们在下一步的中间件也可以配置策略,这里就不细说了,防止混淆。
CORS的配置一定要放在AutoFac前面,否则builder.Populate(services);后,你再进行配置会没有效果。
3、启动中间件
在启动文件 的 中间件管道配置 Configure 种,添加启用Cors中间件服务,但是千万要注意顺序。
public void Configure(IApplicationBuilder app)
{ ... app.UseStaticFiles(); app.UseRouting(); app.UseCors();//添加 Cors 跨域中间件 app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
注意:如果你使用了 app.UserMvc() 或者 app.UseHttpsRedirection()这类的中间件,一定要把 app.UseCors() 写在它们的上边,先进行跨域,再进行 Http 请求,否则会提示跨域失败。
因为这两个都是涉及到 Http请求的,如果你不跨域就直接转发或者mvc,那肯定报错。
4、运行调试,一切正常
至此,跨域的问题已经完成辣,我们通过分离后的,前端的项目工程,来访问api,已经成功了,这里会有两个常见的问题,这里简单列举一下:
5、IIS 部署常见的跨域错误
1、如果遇到了跨域失败的提示,比如这样:
这个并不一定是没有配置好导致的跨域失败,还有可能是接口有错误,比如 500了,导致的接口异常,所以就提示访问有错误。
2、可能部署到服务器的时候,会出现 Put 和 Delete 谓词不能用的问题。
这个很简单,是因为 IIS 不支持,添加进去进行了,在发布好的 web.config 文件里:
①删除IIS安装的WebDav模块,选择你的项目,右边有个“模块”,双击它;找到WebDavModule,删除它。
<modules runAllManagedModulesForAllRequests="true" runManagedModulesForWebDavRequests="true"> <remove name="WebDAVModule" />
</modules>
<handlers> <remove name="WebDAV" />
</handlers>
现在咱们继续聊聊 CORS 的优劣有哪些:
优势:
1、支持所有的 Http 谓词请求;
2、支持多种输出格式,主要是json;
3、可用在生产环境;
4、同时配置多个前端项目;
劣势:
1、配置太偏重后端;
2、会暴露后端api域名或端口;
从上边咱们可以看出来,CORS 优点还是很多的,我们平时的开发基本也是使用的这个,应用范围也特别的广泛,但是也是有一两个小问题的,就比如我们平时开发的时候,可能时不时前端vue项目就会修改端口,那就只能让后端工程师来修改配置了。
亦或者,虽然接口数据很正常被获取,但是接口地址还是不想暴露出去,欸?!那咋办,有办法,就说说今天的第二个重头戏 —— Proxy 代理!
四、webpack 的 proxy 代理
1、Vue-Cli 3.0 新增全局配置文件 vue.config.js
vue项目搭建的时候,会有一个全局config配置文件,在 vue-cli 2.0 脚手架中,很明显的把它放到了 config 的一个文件夹中,是这样的,我们在 index.js 中可以端口号的配置,打包之后路径的配置,图片的配置 等等
但是 vue-cli 3.0 脚手架中,去掉了 config 这个文件夹,那我们如何配置呢,我们可以在项目根目录新建一个 vue.config.js 文件,像之前的很多繁琐配置,都可以在这个文件里配置啦。官方说明,vue.config.js 是一个可选的配置文件,如果项目的 (和 package.json 同级的) 根目录中存在这个文件,那么它会被 @vue/cli-service 自动加载。你也可以使用 package.json 中的 vue 字段,但是注意这种写法需要你严格遵照 JSON 的格式来写。
我们就在根目录下新建该文件,然后添加内容:
module.exports = { // 基本路径 baseUrl: "/", // 输出文件目录 outputDir: "dist", // eslint-loader 是否在保存的时候检查 lintOnSave: true, // webpack配置 // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md chainWebpack: () => {}, configureWebpack: () => {}, // 生产环境是否生成 sourceMap 文件 productionSourceMap: true, // css相关配置 css: { // 是否使用css分离插件 ExtractTextPlugin extract: true, // 开启 CSS source maps? sourceMap: false, // css预设器配置项 loaderOptions: {}, // 启用 CSS modules for all css / pre-processor files. modules: false }, // use thread-loader for babel & TS in production build // enabled by default if the machine has more than 1 cores parallel: require("os").cpus().length > 1, // PWA 插件相关配置 // see https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa pwa: {}, // webpack-dev-server 相关配置 devServer: { open: true, //配置自动启动浏览器 host: "127.0.0.1",//主机 port: 6688, // 端口号自定义 https: false,//是否开启https安全协议 hotOnly: false, // https:{type:Boolean} proxy: null, // 设置代理 before: app => {} }, // 第三方插件配置 pluginOptions: { // ... }
};
相应的注释都有,主要是配置 devServer 节点,从名字上也能看出来,这个是 dev 开发环境的服务配置,常用来配置我们的端口号 port ,还有一个就是 proxy 的设置代理。
2、配置 proxy 本地代理
将上边的 proxy: null 注释掉,然后修改代理设置:
这样,我们就把接口地址代理到了本地,那代理到本地,如何调用呢,请往下看。
3、修改接口api地址,http.js文件
还记得我们在 src 文件夹下有一个 api/http.js 文件么,这个就是配置我们的 http 请求相关的,其他的都不变,我们只需要把域名去掉即可,或者写上本项目的域名:
var root = "/api/";//配置 proxy 代理的api地址,
其实说白了,就是在项目启动的时候,在node服务器中,是把所有的
/api开头的接口字符串,也就是这样的http://localhost:6688/api的都指向了
http://xxxx:8081 域名,这样就实现了跨域
其他任何都不需要变,接口的使用还是原来的使用方法,这样,我们在本地开发的时候,就可以获取到后端api数据了,不用再去 .net core 中设置跨域CORS了,是不是很方便。
说句简单的:就是把后端的端口,给代理到了当前的前端端口,实现了跨域,就好像 node 服务,作为要给代理人的身份,来处理。
4、本地浏览效果
记得我们修改 vue.config.js 后要重启下服务,然后就可以看到项目成功获取数据,并代理到本地:
可以看到,我们已经把远程接口数据 123.206.33.109 成功的代理到了本地 localhost:6688 ,是不是很简单!
5、build 打包发布 IIS
那我们本地开发好了,是不是一切都稳妥了呢,我们可以试一试,通过 build 打包,生成 dist 文件夹,然后将文件夹拷贝到服务器,并配置 IIS ,这个很简单,就和配置普通静态页面是一样的,
我们发现虽然页面可以浏览(可以渲染,证明我们的 vue 已经生效),但是却获取不到数据,这证明我们上边的 proxy 代理,只是适用本地dev开发环境中:
虽然这个本地代理的方法很简单,特别适合我们独立开发,在跨域这一块,完全不用和后端做处理,但是服务器生产环境是不行的,那怎么办,既然本地的 node 服务可以代理,那打包后的 html 静态项目,有没有一个人站出来,充当代理的角色呢,哎!还真有,就是Nginx;
五、基于Nginx 的反向代理
这篇文章仅仅是如何使用 Nginx 作为一个反向代理服务器,具体的深入原理以及负载均衡器等等,我会在以后的微服务系列中说到(不知不觉又给自己玩了一个坑?)。
1、Nginx的代理工作原理
反向代理(Reverse Proxy)方式是指以代理服务器来接受 Internet上 的连接请求,然后将请求转发给内部网络上的服务器;并将从服务器上得到的结果返回给 Internet 上请求连接的客户端,此时代理服务器对外就表现为一个服务器。
通常的代理服务器,只用于代理内部网络对 Internet 外部网络的连接请求,客户机必须指定代理服务器,并将本来要直接发送到 Web 服务器上的 http 请求发送到代理服务器中。不支持外部网络对内部网络的连接请求,因为内部网络对外部网络是不可见的。当一个代理服务器能够代理外部网络上的主机, 访问内部网络时,这种代理服务的方式称为反向代理服务。此时代理服务器对外就表现为一个Web服务器,外部网络就可以简单把它当作一个标准的 Web 服务器 而不需要特定的配置。不同之处在于,这个服务器没有保存任何网页的真实数据,所有的静态网页或者CGI程序,都保存在内部的Web服务器上。因此对反向代 理服务器的攻击并不会使得网页信息遭到破坏,这样就增强了Web服务器的安全性。
总结来说呢,就是我们通过 nginx 反向代理服务器处理我们的请求,具体的数据处理还是交给 IIS,然后得到处理过的数据以后,我们再发送给 Internet 请求的客户端,这样就不会存在跨域的问题了。
2、Nginx 下载与使用
下载地址:http://nginx.org/en/download.html
我选择下载稳定版 1.14 ,下载好后解压,然后就看到根目录下的 Nginx.exe ,直接双击即可开启服务,然后就会在任务管理器查看到已经启动的 Nginx 代理服务。
因为默认的是80端口,大家的端口应该都已经被占用,所以我们需要修改端口
打开 config 文件夹下的 nginx.conf 文件,然后修改端口号
这个很简单,这个时候记得要重启 Nginx 服务,你可以采用命令的形式,不过我有时候会发现无效,我一般使用的时候,在任务管理器中把所有的 nginx 进程全部结束,然后双击 nginx.exe 开启。
这个时候我们直接访问 localhost:8077 就发现已经启动成功了,只不过是 nginx 的欢迎页。
这个时候你一定好奇,为什么仅仅配置下,就能访问该端口呢,不信的话,你可以在 cmd 中 通过 netstat -an 命令来查看 8077 端口是否被使用
发现已经被监听使用,如果还不相信,你可以创建一个 IIS 项目,然后配置 8077 端口,发现会报错,这也就是说明了,8077端口已经被占用,准确来说是被 Nginx 占用的,所以,Nginx 和 IIS一样都是可以作为反向代理服务器来使用,从而可以通过监听端口来代理我们的项目的:
3、将上文打包后的 dist 文件,配置 Nginx 代理
1、将我们上边 build 后的 dist 文件,放到咱们下载的 nginx 下的 html文件夹
2、配置代理
还是我们的 config/nginx.conf 文件,打开并配置 本地代理 ,注意注释是用 # 号,不是 //
server { listen 8077;#监听端口,也就是我们的项目端口 server_name localhost;#服务器主机 location / { root html;#监听文件夹,默认是nginx下的html文件夹,也可以自定义物理路径 E:\\Nginx\\test index index.html index.htm;#默认首次启动的文件 } #配置本地代理规则 location /api {#名字取一个 api rewrite ^.+apb/?(.*)$ /$1 break; #路径重写机制(无用,但是你也可以自己定义,对路由进行变化) include uwsgi_params; proxy_pass http://123.206.33.109:8081; #api接口的域名地址 }
相应的注释已经写好了,自己看看就明白了,和上边 node 的proxy代理是一个逻辑。
4、本地以及服务器发布预览
这个时候,你再访问 localhost:8077 就能看到我们的项目内容了,访问页面也能看到我们的数据了,代理成功!
这个时候仅仅是本地,那服务器行不行呢,我们只需要将我们的 nginx 文件夹拷贝到服务器,并且双击 nginx.exe 启动代理服务,然后就可以啦!
是不是很简单,只需要把http.js 的baseurl 修改一下,
完全不用修改我们任何的调用接口地址,也不用修改前端代理,更不用修改后端配置,就可以实现跨域。
5、刷新后出现 404
如果是IIS部署
1、如果你是 IIS 部署,就使用 hash 模式;
2、如果用IIS,也想用 history 模式,可以配置 URL重写:https://router.vuejs.org/zh/guide/essentials/history-mode.html#后端配置例子
如果是Nginx部署:
1、不过如果用 nginx 的话,可以利用 404 页面的机制,将 index.html 页面 copy 一份,重命名成 404.html 即可;
2、如果不想添加一个 404 文件的话 ,就直接修改下 nginx 的配置文件(推荐)
官方解释:https://router.vuejs.org/zh/guide/essentials/history-mode.html#%E5%90%8E%E7%AB%AF%E9%85%8D%E7%BD%AE%E4%BE%8B%E5%AD%90
六、结语
0、不跨域 —— 前后端写在一起,我还真的有一个项目是把Vue 和 .net 整合到一起了,不说明 ❤;
七、Github
https://github.com/anjoy8/Blog.Core