本文有配套视频: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 的:
代码语言:javascript复制<!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
代码语言:javascript复制 /// <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 中添加
代码语言:javascript复制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中间件服务,但是千万要注意顺序。
代码语言:javascript复制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 文件里:
代码语言:javascript复制①删除IIS安装的WebDav模块,选择你的项目,右边有个“模块”,双击它;找到WebDavModule,删除它。 ②修改你项目的web.config ,在<system.webServer>标签内加上以下代码。
<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 的格式来写。
我们就在根目录下新建该文件,然后添加内容:
代码语言:javascript复制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/@vue/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 注释掉,然后修改代理设置:
代码语言:javascript复制 proxy: {
// 配置多个代理
"/api": {//定义代理名称
target: "http://xxxx:8081",//我们的接口域名地址
ws: true,
changeOrigin: true,//允许跨域
pathRewrite: {
// 路径重写,
"^/api": "" // 替换target中的请求地址,这样请求地址种,就不包含api这个字符串了
}
}
},
这样,我们就把接口地址代理到了本地,那代理到本地,如何调用呢,请往下看。
3、修改接口api地址,http.js文件
还记得我们在 src 文件夹下有一个 api/http.js 文件么,这个就是配置我们的 http 请求相关的,其他的都不变,我们只需要把域名去掉即可,或者写上本项目的域名:
代码语言:javascript复制// 配置API接口地址
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 作为一个反向代理服务器,具体的深入原理以及负载均衡器等等,我会在以后的微服务系列中说到(不知不觉又给自己玩了一个坑