--- title: 前后端跨域问题 categories: - 开发 tags: - 编码 - 反向代理 - Nginx - ingress - 浏览器 abbrlink: lpuzlvhd date: 2023-12-07 17:19:56 --- ## 啥是跨域 跨源资源共享(CORS,或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其他源(域、协议或端口),使得浏览器允许这些源访问加载自己的资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的“预检”请求。在预检中,浏览器发送的头中标示有 HTTP 方法和真实请求中会用到的头。 跨源 HTTP 请求的一个例子:运行在 https://domain-a.com 的 JavaScript 代码使用 XMLHttpRequest 来发起一个到 https://domain-b.com/data.json 的请求。 出于安全性,浏览器限制脚本内发起的跨源 HTTP 请求。例如,XMLHttpRequest 和 Fetch API 遵循同源策略。这意味着使用这些 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源,除非响应报文包含了正确 CORS 响应头。 ![image.png](https://static.zahui.fan/images/202312061646763.png) ## 为什么会跨域 说到跨域不得不谈的就是浏览器的同源策略,跨域也是因为浏览器这个机制引起的,这个机制的存在还是在于安全。 ### 什么是源 Web内容的源由用于访问它的URL 的方案(协议),主机(域名)和端口定义。只有当方案,主机和端口都匹配时,两个对象具有相同的起源。 同源不同源一句话就可以判断:就是url中 scheme host port 都相同即为同源。 ### URL结构 URL 代表着是统一资源定位符(Uniform Resource Locator)。URL 无非就是一个给定的独特资源在 Web 上的地址。 URL有如下结构组成: Scheme 或者 Protocol, 常见的就是http或者https ![image.png](https://static.zahui.fan/images/202312061647029.png) Domain Name 也叫做host域名 ![image.png](https://static.zahui.fan/images/202312061648575.png) port 端口号 ![image.png](https://static.zahui.fan/images/202312061648625.png) Parameters参数 ![image.png](https://static.zahui.fan/images/202312061648585.png) Anchor 锚点,一般用于定位位置 ![image.png](https://static.zahui.fan/images/202312061648639.png) ### 同源不同源举例 *就是url中 scheme host port 都相同即为同源。* 下面是几个不同源的例子, 如果访问就会产生跨域问题. | 前端域名 | 后端域名 | 原因 | | ------------------------ | ----------------------- | ---------------------------------- | | https://example.com/ | http://example.com/api | 协议不一样 | | http://example.com | http://api.example.com | host不一样 | | http://example.com | http://example.com:8080 | 端口不一样 | ## 跨域问题的症状 ### 有跨域问题 ![image.png](https://static.zahui.fan/images/202312061635874.png) ### 正常情况 ![image.png](https://static.zahui.fan/images/202312061643686.png) ## 后端解决方案 三、后端解决方案 后端框架也很多,实现原理差不多,都是修改下相应头。以常用的Java SpringCloud 和nodejs koa 框架为例。 Http 协议CORS头 跨域其实也是http层面上可以解决的问题,后端解决也是比较简单的,也是项目常见的解决手法。 CORS (Cross-Origin Resource Sharing,跨域资源共享)是一个系统,它由一系列传输的HTTP头组成,这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应。 同源安全策略 默认阻止“跨域”获取资源。但是 CORS 给了web服务器这样的权限,即服务器可以选择,允许跨域请求访问到它们的资源。其实就是一些Header头: ```bash Access-Control-Allow-Origin 指示请求的资源能共享给哪些域。 Access-Control-Allow-Credentials 指示当请求的凭证标记为 true 时,是否响应该请求。 Access-Control-Allow-Headers 用在对预请求的响应中,指示实际的请求中可以使用哪些 HTTP 头。 Access-Control-Allow-Methods 指定对预请求的响应中,哪些 HTTP 方法允许访问请求的资源。 Access-Control-Expose-Headers 指示哪些 HTTP 头的名称能在响应中列出。 Access-Control-Max-Age 指示预请求的结果能被缓存多久。 Access-Control-Request-Headers 用于发起一个预请求,告知服务器正式请求会使用那些 HTTP 头。 Access-Control-Request-Method 用于发起一个预请求,告知服务器正式请求会使用哪一种 HTTP 请求方法。 Origin 指示获取资源的请求是从什么域发起的。 ``` ```java @Configuration public class GatewayCorsConfiguation { @Bean public CorsFilter corsFilter(){ // 初始化cors配置对象 CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowCredentials(true); // 允许使用cookie,但是使用cookie是addAllowedOrigin必须是具体的地址,不能是* // configuration.addAllowedOrigin("*"); configuration.addAllowedOrigin("http://manage.leyou.com"); configuration.addAllowedMethod("*"); //允许的请求方式,get,put,post,delete configuration.addAllowedHeader("*");//允许的头信息 //初始化cors的源对象配置 UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource(); corsConfigurationSource.registerCorsConfiguration("/**",configuration); //3.返回新的CorsFilter. return new CorsFilter(corsConfigurationSource); } } ``` ## 运维解决方案 ### Nginx ingress 配置 增加注解 ```bash nginx.ingress.kubernetes.io/cors-allow-credentials: 'true' nginx.ingress.kubernetes.io/cors-allow-headers: >- x-ingeek-nonce,x-ingeek-timestamp,X-Ingeek-Signvalue,X-ingeek-Appid,X-ingeek-Client-Version, DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization nginx.ingress.kubernetes.io/cors-allow-methods: 'PUT, GET, POST, OPTIONS' nginx.ingress.kubernetes.io/cors-allow-origin: '*' nginx.ingress.kubernetes.io/enable-cors: 'true' ``` ### Nginx配置 #### 使用 proxy_pass 转发 此方法是将后端地址反代到前端的一个location, 这样就是同源了. ```conf location /api { proxy_pass https://b.test.com; # 设置代理服务器的协议和地址 } ``` #### 使用Nginx增加Header ```conf server { listen 80; server_name www.example.com; root /usr/share/nginx/html; location / { proxy_pass http://localhost:8188/; # 设置是否允许 cookie 传输 add_header Access-Control-Allow-Credentials true; # 允许请求地址跨域 * 做为通配符 add_header Access-Control-Allow-Origin * always; # 允许跨域的请求方法 add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS'; add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; if ($request_method = 'OPTIONS') { return 204; } } } ``` ## 常见问题 ### 后端服务和Nginx同时处理了跨域 跨域问题后端和Nginx不要同时处理, 不然会报错: ![image.png](https://static.zahui.fan/images/202312071706036.png) 这个时候删除Nginx上的 Access-Control-Allow-Origin 配置即可, 建议后端和Nginx不要同时配置跨域. ### Header不在允许的列表中 出现跨域问题的时候, 可以在浏览器的控制台看到详情, 比如下图提示 `x-ingeek-client-version` 这个header 不在 `Access-Control-Allow-Headers` 中 ![image.png](https://static.zahui.fan/images/202312061638756.png)