什么是跨域
关于什么是跨域的问题相信很多开发者都耳熟能详,并且也能很好地回答出来,这里我就简单解释一下不多加赘述了,简单来说就是你在当前网页的域名下去请求另一个域名下的资源就会出现跨域的情况。请求地址的协议,域名,端口号三者只要有一个和当前的不一样就是跨域。对于前端开发者有时候可能不那么容易去调式一个跨域的情况,本文就用一个简单的例子可以让前端开发者很好的理解并去解决这个问题。
一个简单的跨域示例
- 首先创建一个最简单的
index.html
页面,页面只包含一个button
,点击这个按钮的时候会发送一个到http://localhost:3030/user
的请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=<device-width>, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h2>Test for Cross Domain</h2>
<button id="crossFetch">Cross Fetch</button>
</body>
<script>
const crossFetch = document.querySelector('#crossFetch');
crossFetch.addEventListener('click', () => {
fetch('http://localhost:3030/user').then(response => {
return response.json();
}).then(res => {
console.log(res);
}).catch((err) => {
console.error('set cookie error: ', err);
})
})
</script>
</html>
- 然后创建一个文件
clientSide.js
, 一个最简单的express
后台服务,让刚才那个页面在http://localhost:8000
这个域名下运行,然后命令行运行node clientSide.js
const express = require('express');
const app = express();
const path = require("path");
app.use(express.static(path.join(__dirname, './public')));
app.listen(8000, () => {
console.log('listening port 8000...');
})
- 打开浏览器输入
localhost:8000/index.html
,然后我们会看到一个只包含一个按钮的页面
- 现在前端是有了,但是后端服务还没有,我们再创建一个文件
serverSide.js
,让这个后端服务运行在3030
端口,并提供了一个/user
获取user的接口,这个其实就是上面点击按钮要发送的地址。同样的启动这个服务node serverSide.js
, 相信没有比这更简单的服务了。
const express = require('express');
const app = express();
app.get('/user', (req, res) => {
res.json({
name: "jacky",
age: 23
});
})
app.listen(3030, () => {
console.log('listening port 3030');
})
- 现在基本条件都准备好了,然后我们去点击页面的按钮去发送请求,这时候我们在网页的控制台会发现一个错误。这个错误就是我们开发中经常遇到的跨域问题。
Access to fetch at 'http://localhost:3030/user' from origin 'http://localhost:8000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is
present on the requested resource. If an opaque response serves your needs,
set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
通过引入第三方包cors
来解决跨域问题
'Access-Control-Allow-Origin' 这个是什么?
在引入包之前我们看下这段报错内容说的是什么,Access-Control-Allow-Origin
这个设置阻止了这次请求,这个设置其实就是后端通常用来限制请求的响应头配置,如果你想让你的资源不受限制,谁都可以访问,就可以设置一个通配符"*":
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
next();
});
或者你也可以指定某些域名可以访问
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "https://www.baidu.com");
next();
});
引入cors
包来解决跨域问题
我们可以通过npm install cors
来安装第三方包,把它作为express
的中间件设置进去,改过之后的serverSide.js
就变成了如下这样,现在我们来重启一下这个文件
const express = require('express');
const app = express();
const cors = require('cors')
app.use(cors()) //不加参数就相当于:res.header("Access-Control-Allow-Origin", "*");
app.get('/user', () => {
res.json({
name: "jacky",
age: 23
});
})
app.listen(3030, () => {
console.log('listening port 3030');
})
现在刷新一下浏览器再去点击按钮,发现控制台的错误就没有了,跨域问题可以通过后端配置很好地解决了。
跨域如何携带cookie
我们都知道cookie
是前端非常重要的一种存储方式,经常用来保持用户的登录状态,那在跨域的情况下如何携带cookie呢?
- 接下来我们来简单改下刚才的
index.html
文件,多添加一个Login
按钮,这个按钮是用来向后端发送请求(可以理解为登录)后,后端会给浏览器设置一个cookie
值。如截图所示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=<device-width>, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h2>Test for Cross Domain</h2>
<button id="Login">Login</button>
<button id="crossFetch">Cross Fetch</button>
</body>
<script>
const Login = document.querySelector('#Login');
const crossFetch = document.querySelector('#crossFetch');
crossFetch.addEventListener('click', () => {
fetch('http://localhost:3030/user').then(response => {
return response.json();
}).then(res => {
console.log(res);
}).catch((err) => {
console.error('set cookie error: ', err);
})
})
Login.addEventListener('click', () => {
fetch('/login').then(response => {
return response.json();
}).then(res => {
console.log(res);
}).catch((err) => {
console.error('set cookie error: ', err);
})
})
</script>
</html>
更改后的clientSide.js
文件如下:
const express = require('express');
const app = express();
const path = require("path");
app.use(express.static(path.join(__dirname, './public')));
app.get('/login', (req, res) => {
res.cookie("user_id", "123", {
maxAge: 1000 * 60 * 60 * 2
});
res.json({
result: "success"
});
})
app.listen(8000, () => {
console.log('listening port 8000...');
})
- 现在点击按钮
Cross Fetch
, 我们在后端的请求/user
接口里打印下看能不能获取刚才设置的cookie值
app.get('/user', (req, res) => {
console.log('cookie:', req.headers.cookie);
res.json({
name: "jacky",
age: 23
});
})
很明显是undefined
, 并不能获取前台发来的cookie值。
为什么会这样呢?因为我们在前端使用的fetch
请求中有个参数叫作credentials
,这个值是用来控制发送cookie的,它一共有三个值:
same-origin
同域名下可以携带cookie发送,这个值也是默认值include
允许跨域携带cookie发送omit
不携带cookie
那这样就很好办了,我们把这个值设置成include
不就好了,来我们再来改一下index.html
:
...
fetch('http://localhost:3030/user', {
credentials: 'include'
}).then(response => {
return response.json();
})
...
看着很完美地样子,我们去刷新浏览器,再去点击Cross Fetch
按钮,但是控制台又有了新的错误:
Access to fetch at 'http://localhost:3030/user' from origin 'http://localhost:8000'
has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin'
header in the response must not be the wildcard '*' when the request's credentials
mode is 'include'.
简单总结就是,如果前端credentials设置成了include
,那么后端的响应头Access-Control-Allow-Origin
就不能设置为通配符“*”了。那我们只能再来去改下serverSide.js
里的配置,我们指定域名而不是给通配符(其实通配符是不推荐使用的方法)来访问资源,查了下文档,可以传参给cors
方法。这里设置了两个参数,一个origin
指的就是指定域名,第二个credentials
就是允许携带cookie
。
app.use(cors({
origin: 'http://localhost:8000',
credentials: true
}));
最后我们保存下文件重新运行下serverSide.js
,然后刷新浏览器,点击Cross Fetch
按钮,现在发现服务端就可以获取到cookie
值了,跨域携带cookie的问题也很好地解决了。
总结
跨域是我们开发中经常遇到的问题,解决的办法有很多种,希望大家也可以动手去试试不同的解决方法,虽然都是一些基本的知识,但是积累的多了,当你再遇到类似的问题就可以很快地找到答案了,半夜码字不易,喜欢的小伙伴给个赞吧!!!