一个简单的案例带你了解并解决跨域以及跨域携带cookie的问题

1,021 阅读5分钟

什么是跨域

关于什么是跨域的问题相信很多开发者都耳熟能详,并且也能很好地回答出来,这里我就简单解释一下不多加赘述了,简单来说就是你在当前网页的域名下去请求另一个域名下的资源就会出现跨域的情况。请求地址的协议,域名,端口号三者只要有一个和当前的不一样就是跨域。对于前端开发者有时候可能不那么容易去调式一个跨域的情况,本文就用一个简单的例子可以让前端开发者很好的理解并去解决这个问题。

一个简单的跨域示例

  • 首先创建一个最简单的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,然后我们会看到一个只包含一个按钮的页面

image.png

  • 现在前端是有了,但是后端服务还没有,我们再创建一个文件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');
})

现在刷新一下浏览器再去点击按钮,发现控制台的错误就没有了,跨域问题可以通过后端配置很好地解决了。

image.png

跨域如何携带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...');
})

image.png

  • 现在点击按钮Cross Fetch, 我们在后端的请求/user接口里打印下看能不能获取刚才设置的cookie值
app.get('/user', (req, res) => {
   console.log('cookie:', req.headers.cookie);
   res.json({
       name: "jacky",
       age: 23
   });
})

很明显是undefined, 并不能获取前台发来的cookie值。

image.png 为什么会这样呢?因为我们在前端使用的fetch请求中有个参数叫作credentials,这个值是用来控制发送cookie的,它一共有三个值:

  1. same-origin 同域名下可以携带cookie发送,这个值也是默认值
  2. include 允许跨域携带cookie发送
  3. 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的问题也很好地解决了。

image.png

总结

跨域是我们开发中经常遇到的问题,解决的办法有很多种,希望大家也可以动手去试试不同的解决方法,虽然都是一些基本的知识,但是积累的多了,当你再遇到类似的问题就可以很快地找到答案了,半夜码字不易,喜欢的小伙伴给个赞吧!!!