记录一次跨域问题的排查
1、环境
前端是单独部署在A服务器上的,后端gateway和Java微服务是部署在B服务器上的,A服务器上的Nginx配置了请求转发,整个请求链路如下:浏览器->A服务器Nginx->B服务器gateway->B服务器java微服务
2、遇到问题
前端同学本地调试过程中,出现了跨域的问题。理论上前端部署在A服务器上,Nginx也部署在A服务器上,前端访问的经过Nginx转发的地址,不会出现跨域问题的,但是问题确实出现了,正好学习了解一下跨域产生的原因和常规的处理方法。
3、跨域产生的原因
跨域是由浏览器的同源策略导致的,目的是防止攻击,确保网站访问的是自己受信任的域名,当协议、ip、端口三者任意一个不一样时,会认为是跨域。
浏览器解决跨域的方式就是CORS(跨域资源共享),浏览器能访问不同源的资源,由资源服务端决定,如果资源端允许当前origin的请求,则在请求头Access-Control-Allow-Origin返回请求头origin本身或者*即可,流程如下:
跨域主要设计到4个响应头:
Access-Control-Allow-Origin 用于设置允许跨域请求源地址 (预检请求和正式请求在跨域时候都会验证)
Access-Control-Allow-Headers 跨域允许携带的特殊头信息字段 (只在预检请求验证)
Access-Control-Allow-Methods 跨域允许的请求方法或者说HTTP动词 (只在预检请求验证)
Access-Control-Allow-Credentials 是否允许跨域使用cookies,如果要跨域使用cookies,可以添加上此请求响应头,值设为true(设置或者不设置,都不会影响请求发送,只会影响在跨域时候是否要携带cookies,但是如果设置,预检请求和正式请求都需要设置)。
跨域请求时会先发送预检请求,浏览器首先会询问服务器,当前网页所在的域名是否在服务器的许可列表中,以及可以使用的请求头和请求方法。若得到肯定的答复,才会发送正式请求Xhr请求,否则报错
从上面的时序图可以知道,关键点就是响应头里面需要增加Access-Control-Allow-Origin,所以解决方法就有如下三种:
3.1、Nginx的反向代理
- 如果Nginx和客户端在同一域名下,通过Nginx反向代理服务端,客户端访问的Nginx的地址,则不存在跨域问题。
- 如果Nginx和客户端不在同一域名下,可以在Nginx上配置响应头,解决跨域问题,配置如下:
server { listen 8080; server_name localhost; location / { add_header Access-Control-Allow-Origin 'http://localhost:5501' always; add_header Access-Control-Allow-Headers '*'; add_header Access-Control-Allow-Methods '*'; add_header Access-Control-Allow-Credentials 'true'; if ($request_method = 'OPTIONS') { return 204; } proxy_pass http://localhost:9090; } }
3.2、gateway统一增加响应头
如果请求没有经过Nginx,或者Nginx没有配置跨域,则可以在gateway配置跨域,在application.propertity中增加跨域配置,方法如下:
spring: cloud: gateway: globalcors: #允许options请求 add-to-simple-url-handler-mapping: true cors-configurations: '[/**]': #允许哪些网站的跨域请求 allowedOriginPatterns: "*" #允许的跨域ajax请求 allowedMethods: - "GET" - "POST" - "DELETE" - "PUT" - "OPTIONS" #允许在请求头中携带的信息 allowedHeaders: "*" #是否允许携带cookie allowCredentials: true #这次跨域检测的有效期 maxAge: 360000
3.3、java微服务解决
如果Nginx没有配置跨域、gateway也没有配置跨域,则可以在java微服务里面配置,一般的配置也就两种,一种是全局配置,实现addCorsMapping方法和局部的@CrossOrigin注解
3.3.1、重写WebMvcConfigurer接口的默认方法
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") //是否发送Cookie .allowCredentials(true) //放行哪些原始域 .allowedOrigins("*") .allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"}) .allowedHeaders("*") .exposedHeaders("*"); } }
3.3.2、@CrossOrigin
这个注解可以在类和方法上使用,在类上使用,则类里面所有的Controller接口都支持跨域,在方法上使用,则该方法支持跨域,详细使用方法自行百度。
4、问题解决
本次遇到的问题,是gateway上的application.properyity中的配置项key写错了,导致没有生效,奇怪的是我们的java微服务本身也做了跨域,理论上网关未生效,也是可以支持跨域的,然后我们修改了网关的跨域后,出现了新的问题,由于网关和java微服务都做了跨域,导致响应头重复了(这是正常现象,一条链路上,多个地方处理跨域就会重复),针对这个问题,在网关中针对指定路由中重复的响应头做处理,示例配置如下:
spring: cloud: gateway: routes: - id: test uri: lb://test predicates: Path=/api/** filters: - DedupeResponseHeader=Access-Control-Allow-Origin, RETAIN_FIRST
RETAIN_FIRST和RETAIN_UNIQUE两种策略,前者是取第一个,后者是键和值相同就去重保留一个,键相同值不同,还是会有两个,一般我们java微服务的请求头是先加进去的,取第一个没有问题。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」