type
status
date
slug
summary
tags
category
icon
password
AI summary
跨域基础知识
跨域相关的基础知识,阮一峰老师的文章已经非常全面了。建议还不太了解的同学必须要先去看看。https://www.ruanyifeng.com/blog/2016/04/cors.html
我碰到的跨域问题
spring-cloud-gateway + downstream
我们应该是从2年开始,开始尝试使用spring-cloud-gateway。增加一层gateway后整体架构如下:

其中,a-web是需要支持跨域访问的,所以在a-web里已经有相关的跨域配置:
不过在引入gateway之后,跨域出现了问题:
不过,这里只有需要发起预检(Preflight)的复杂请求会报错,在预检请求里就会报跨域异常。简单请求是可以正常响应的。看了源代码发现gateway会拦截预检请求并处理,不会发送到下游

而我们最初在gateway这一层是没有配置跨域的,于是尝试在gateway这一层也增加跨域配置
不过,配置之后,跨域请求还是没能成功,这次又抛出一个新的异常:
正常的跨域响应头里应该只包含一个
Access-Control-Allow-Origin
,而我们的响应头里面包含了2个。原因就是a-web和gateway都有跨域的配置。a-web处理完请求之后已经在响应头上加了Access-Control-Allow-Origin
然后返回给gateway,而gateway没有兼容处理响应头里已经有Access-Control-Allow-Origin
的场景。所以导致了响应头里有多个Access-Control-Allow-Origin
。这个问题网上有很多文章都提到了,也提供了两种思路去修改:
- 通过自定义GlobalFilter来手动移除下游服务端添加的
Access-Control-Allow-Origin
- 通过spring-cloud-gateway自带的多个相同响应头的处理机制,配置保留策略,保留一个头即可
而我们采用了一种更加简单的方式去解决这个问题:让下游的服务端能识别出这个请求是通过gateway过来的,还是没经过gateway直接访问的。只有在处理直接访问的请求时,下游才需要加上跨域的逻辑。于是我们自定义了一个新的CustomerCorsFilter,用来替换前面的CorsFilter:
spring-cloud-gateway + spring-cloud-gateway
不过,又一个场景的出现,让我们不得不使用其他方式来解决这个问题。引入spring-cloud-gateway之后,我想把整体架构升级,把xxx-web这一层废弃,web接口直接下沉到对应的领域服务里。gateway可以通过url直接提取到路由信息,所以必须规范url的命名。但是之前已经定义好的那些“不规范”的url,我们需要通过自定义转发规则来把它规范化。
这里还要提到我们的灰发环境:我们的灰发是通过生产环境的gateway来做的,灰发的服务会以
服务名称_pre
的形式注册到gateway的注册中心。请求到达生产gateway后,会根据请求的特征(比如ip、用户id等)做路由计算,判断是走灰发还是走生产。但是当我们想要灰发上面提到的转发规则的时候,这条路就行不通了。我们必须要把这套转发规则配置在灰发环境。所以,“软灰发”行不通了,我们只能来点硬的。于是增加一种硬灰发的策略——识别出需要走灰发的请求,直接把它转发到灰发环境的域名,走完整的灰发slb->灰发gateway的路径。而这样通过gateway到gateway再到底层服务的路径,就暴露了之前的问题,我们无法避免在gateway这一层做调整了。于是我们采用了自定义GlobalFilter的方式来删除下游添加的跨域响应header
具体参考的下面这篇文章,应该是当时我看到的关于这个内容写得最好的一篇文章了:
跨域白名单
由于前面一直图省事,配置的都是
*
,但是一些安全机构要求不允许跨域通配,所以只能改成跨域白名单。但是我们一个gateway支持了很多域名,找前端同学都无法全部枚举出来,只能通过打开日志收集origin头。收集了一段时间,差不多有100+个域名(因为我们是平台型的供应商,所以给不少租户都提供了子域名)。如果这些子域名都要一个个手动配的话,就非常麻烦,并且后续如果要增加子域名的话还要多一步配置跨域的步骤,所以我们期望可以通过通配来配置跨域白名单。不过遗憾的是,spring-cloud-gateway暂时不支持跨域通配,只支持
*
。我们找到对应的跨域处理类CorsProcessor
,稍微调整了一下逻辑:更进一步的,我们结合了我们的配置中心做跨域配置的实时生效
最后,替换默认的
CorsProcessor
前端代理请求也会涉及到跨域?
之前以为只有浏览器发起的请求才会有跨域一说(毕竟同源策略应该是浏览器的安全策略),这可能是一个错误的认知。其实只要你的请求里带了origin头,并且服务端有跨域的配置,服务端就会把这个请求当做一个跨域请求处理。所以,前端代理发起的请求或者是后端服务发起的请求,只要请求头里带了origin,那么就有可能跨域请求失败。
如果服务端没有配置跨域,默认其实是不会处理origin头的,也就是说,你带了origin头也能请求成功。但是,响应体里也不会有相应的支持跨域的头,如果你是通过浏览器发起的请求,那响应体里没带相应的跨域头,浏览器里自然就报错了,虽然这次请求到后端服务是成功的。
另外,前端代理还有一个比较迷惑的配置参数,叫做changeOrigin,乍一看,以为是修改origin头的,但是实际上是用来修改host头的。可以看看下面这篇文章
一个奇怪的options请求
我们知道,一个简单请求是不需要发起options请求的,比如下面这个:
但是,我们在chrome的console发起fetch请求时,发现它竟然发起了一个options请求

更为奇怪的是,在options请求跨域失败的情况下,竟然还发起了GET请求,并通过了跨域检查,得到了相应数据(我通过后台日志发现请求确实到达了downstream server,2个请求都有过去)

不知道是不是在https下请求http的原因导致的?我尝试在http里发起上面的请求,但是貌似被拦截了:

看起来好像是跟我localhost这个domain也有一定的关系,于是我做了内网穿透,换了个域名,发现此时是可以发起跨域请求的,并且也没有options请求,服务端也收到的请求并成功返回了。但是由于没有跨域配置所以被浏览器丢弃了

于是我尝试在https下也把跨域请求的域名从localhost替换成内网穿透的域名,发现这次请求由于是https->http的直接被浏览器block了

这里需要注意的是,在没有任何跨域配置的情况下,gateway只会拦截options请求,而对于非options的请求都会转发到下游去处理。所以你会看到,其实第二个请求成功了,只是响应体里没有对应的跨域头,所以被浏览器丢弃了
参考
- Author:黑微狗
- URL:https://blog.hwgzhu.com/article/spring-cloud-gateway-with-cors
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts