同源策略
同源策略是浏览器的一个重要安全机制,它用于限制一个来源的文档或脚本如何能够与另一个来源的资源进行交互。同源策略要求只有当两个URL的协议、主机和端口都相同时,才被认为是同源。否则,浏览器会认为它们是跨域的。
在早期,服务器端渲染的应用通常不会有跨域问题,因为前端代码和后端API都是在同一个服务器上运行的。随着前后端分离的出现,前端代码和后端API经常部署在不同的服务器上,这就引发了跨域问题。
跨域问题的产生
先来看一下跨域问题怎么产生的:
首先我们在localhost:8000上提供了一个api接口,然后通过nodemon去运行这个node程序。
新建一个html文件,文件中使用fetch向localhost:8000发送请求,并使用LiveServer打开这个html文件
由于LiveServer运行在5501端口而node程序运行在8000端口,不同源,因此产生了跨域问题。
先上解决方案:
- 把API和静态资源部署到同一个服务器上
- CORS(Cross Origin Resource Sharing),跨域资源共享
- node代理服务器(webpack配置proxy代理就是这个),这个是开启了一个node服务器代理,然后由这个node代理请求到资源以后再把资源返回给客户端,因为同源策略的限制是浏览器的限制,而node服务器发送请求是不会受到同源策略限制的,所以不会出现跨域问题。这个node代理服务器使用到的是一个http-proxy-middleware插件进行代理的
- Nginx反向代理
接下来我们来细说这些方案:
把API和静态资源部署到同一个服务器上且同一个端口下:
上面代码可以指定浏览器访问8000端口的时候,如果要找静态资源去./static文件夹下查找。这样8000端口既提供了api又提供了静态资源,当然这种情况下静态资源和api就是同源的,不会产生跨域问题
使用cors。
cors是一种基于http header的机制,该机制通过允许服务器标示除了它自己以外的其它源(域、协议和端口),使得浏览器允许这些 origin 访问加载自己的资源。
设置的解释:
Access-Control-Allow-Origin:允许所有域名访问(你也可以指定特定的域名,例如'example.com')。
Access-Control-Allow-Methods:允许的HTTP请求方法。
Access-Control-Allow-Headers:允许的HTTP请求头。
另外我们可以了解一下浏览器机制,关于预请求和实际请求:
预检请求(Preflight Request):
对于复杂请求(如使用非简单方法:PUT, DELETE 或自定义头),浏览器会先发送一个OPTIONS请求,询问服务器是否允许跨域请求。
服务器如果同意跨域请求,则返回包含CORS头信息的响应。
实际请求(Actual Request):
如果预检请求被允许,浏览器会发送实际请求,并且会在请求头中包含一些CORS相关的头信息。
服务器在响应中包含CORS头信息,这些信息会被浏览器验证。并允许客户端进行跨域请求。
另外针对复杂请求,因为预检请求是一个options的请求,所以上述代码中我们对options请求进行了单独的处理
这时候我们在5501端口访问8000端口的api,发现也是成功的
使用node代理服务器
在我们平时开发中,我们并不会,也不能直接去修改服务器(当然自己开发的服务器除外),那么开发过程中我们遇到跨域问题应该如何解决呢?相信我们在开发的时候做过类似的配置的:
不管是Webpack,还是Vite,它们底层都是通过开启一个新的Node服务器代理来解决跨域的。原理是vite或Webpack给我们开启了一个服务器,我们请求接口的时候vite或Webpack开启的服务器上请求,然后由这台服务器把我们的请求转发到真实的接口服务器上,而由于同源策略只在浏览器上限制资源的共享,并不限制服务器的请求,所以这样就不会产生跨域问题。
这一段配置的意思是,我在发送请求的时候,如果以/api/开头的请求,node服务器,请你帮我代理到http://localhost:8000 上去请求数据,但是如果没有重写/api路径的话,代理会请求http://localhost:8000/api/{realPath}。 但是因为在http://localhost:8000 上的接口地址是没有/api前缀的,所以请求会报错,这里之所以我们请求的时候写上/api,只是为这个请求路径做一个标识,告诉服务器,这个路径你需要帮我代理,而真实的路径是去请求http://localhost:8000/{realPath} 而不需要中间的/api,所以我们要把/api/给重写成空的字符串。
示例如下:
并且这时候,我们在localhost:8000上也关闭掉原来的cors:
发现也是可以正常访问的:
使用Nginx
使用Nginx解决跨域访问分为2种情况。
<1>静态资源和api都通过nginx服务器做了一层代理(这种情况下不会有跨域问题)
<2>只有api通过nginx做了一层代理,而静态资源仍然从另一台静态资源服务器中获取(这种情况下有跨域问题)
第一种情况去配置nginx如下:
可以看到我们在浏览器上通过localhost:8080进行访问的时候,可以定位到 C:\Users\**\static\index.html 文件,并且在页面中通过
http://localhost:8080/api/users/list
请求接口的时候,可以被转发到http://localhost:8000/users/list。
上面就是情况一:静态资源和api都通过nginx服务器做了一层代理的情况。也就是用户拿静态资源,请求api都经过nginx服务器,由nginx服务器统一去请求静态资源和api数据,最后返回给用户。
但是,如果我们通过liveServer打开html的时候,就会产生跨域问题,因为此时静态资源是从localhost :5501里面获取的,而页面上去调用接口的地址是http://localhost:8080/api/user/list。 虽然这个地址会被nginx代理到 http://localhost:8000/user/list ,但是仍然会产生跨域问题。
此时加上如下配置,为nginx在8080端口开启的这个服务器开启了cors,指定8080端口可以允许所有源访问。
此时可以看到,跨域问题得到了解决。