前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一次跨域问题的分析

一次跨域问题的分析

作者头像
出其东门
发布2021-08-09 16:13:59
1.2K0
发布2021-08-09 16:13:59
举报
文章被收录于专栏:01二进制01二进制

事件起因

一个需求让我开放一个 HTTP 接口给前端,在联调的过程中,前端请求时出现了一个 CORS 错误,也即跨域问题,错误如下 ?

一开始我的想法是,跨域问题,这我熟啊,在学校写代码的时候就经常遇到,这解决起来不是分分钟的吗。

可更改之后我傻眼了,为什么一直不生效?我陷入了沉思。

在继续描述之前,我们先来了解下到底什么是跨域以及常见的解决方案有哪些。

什么是跨域

所谓跨域,全称是 跨源资源共享 (CORS) Cross- Origin Resource Sharing ,是一种基于 HTTP Header 的机制,该机制通过允许服务器标示除了它自己以外的其它 origin(域,协议和端口),这样浏览器可以访问加载这些资源。

举个例子:运行在 https://domain-a.com 的 JavaScript 代码使用 XMLHttpRequest 分别发起两个请求 ?

代码语言:javascript
复制

由于发请求的页面站点为 domain-a.com,所以请求 A 属于同源请求,domain-a.com 的后台服务器是允许请求。但是请求 B 的站点域是 domain-b.com,如果要发请求到 domain-b.com,就属于跨源访问,出于安全性考虑,浏览器限制脚本内发起的跨源 HTTP 请求。如下图所示:

因此,为了解决上述问题,跨源域资源共享( CORS )机制就应运而生了。该机制允许 Web 应用服务器进行跨源访问控制,从而使跨源数据传输得以安全进行。

CORS 工作机制

跨源资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。

而且对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨源请求。

只有在服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。

大致流程如上图所示,CORS 请求失败会产生错误,但是为了安全,在 JavaScript 代码层面是无法获知到底具体是哪里出了问题。你只能查看浏览器的控制台以得知具体是哪里出现了错误。

在新增的这一组 HTTP 首部字段中,最重要的便是 Access-Control-Allow-Origin,其语法如下:

代码语言:javascript
复制

其中,origin 参数的值指定了允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符,表示允许来自所有域的请求。

例如,下面的字段值将允许来自 http://www.domain-a.com 的请求:

代码语言:javascript
复制

如果服务端指定了具体的域名而非“*”,那么响应首部中的 Vary 字段的值必须包含 Origin。这将告诉客户端:服务器对不同的源站返回不同的内容。

接下来所说的解决方案主要就是围绕这一字段进行的。

Spring 中对于跨域的常见解决方案

本节介绍一下使用 Spring 中对于跨域的常见解决方案,主要分为以下几种。

1.直接设置请求头2.@CrossOrigin 注解3.WebMvcConfigurer 配置4.使用 CorsFilter 拦截器

这一块的知识点网上一搜一大把,这里就不展开说明了。重点回到本次的问题分析。

上述方案使用结束之后仍然失效?

解决这个问题,经历了几个过程。

使用 @CrossOrigin 注解,接口 1、2 请求正常,但该方案不够通用,暂时舍弃。

使用 WebMvcConfigurer 配置的 addCorsMappings 方法配置接口 3 时失败,仍然出现跨域问题。

查找资料发现,这有可能是客户端请求经过的先后顺序问题,当服务端接收到一个请求时,该请求会先经过过滤器,然后进入拦截器中,然后再进入 Mapping 映射中的路径所指向的资源,所以跨域配置在 mapping 上并不起作用,返回的头信息中并没有配置的跨域信息,浏览器就会报跨域异常。

因此才会出现这种情况,当你在项目中使用了该方法配置跨域问题后,再使用自定义的拦截器时,跨域问题的相关配置就会失效,请求依然会报跨域问题的错。

此时我选择了最后一种方案,也即,直接使用 CorsFilter 拦截器。

在配置好拦截器之后,仍然出现跨域问题,此时的我心态崩了。

治标 or 治本

后来,我意外的发现前端在调用接口时的 URL 有问题,并没有按照我给他的规则去拼接 URL,果然,在请求了正确的 URL 之后,跨域问题,随即消失了。

也就是说,整个事件出现的原因是因为请求参数异常。

至此,这个问题其实已经解决了,治标已经完成。

只是,这时我又产生了新的疑问,为什么请求参数异常没有走到业务逻辑处理而是出现了跨域问题 ?️

让我们情景再现一下 ?

代码语言:javascript
复制

代码样例如上,请求情况如下 ?

代码语言:javascript
复制

经师兄提点,猜想是由于系统内部抛了异常被拦截后自动重定向到淘宝错误页,果然,在我直接使用浏览器访问上述 URL 后,果然跳转到了淘宝的错误页。

而系统之所以会报异常,原因出在 @RequestParam 注解上,让我们看一下他的源码

默认该参数是必传的!

如果不传该参数,甚至进不去业务逻辑的错误校验,从而直接被系统捕获了,接下来就是我们刚才熟知的那一幕了。

所以,其实只要设置成 @RequestParam(required = false),或者不使用该注解,即可从根源上解决该问题。

刨根问底一下

其实从问题的解决角度来说,到这里已经可以了,只不过刨根问底一下,为什么请求错误了会跳到淘宝的错误页,而不是显示 tomcat 的错误页呢?

在询问了师兄和查找相关资料后,我发现,是由于 tengine(阿里内部的魔改 Nginx)的 error_page 配置造成的,在 proxy_intercept_errors 配置成功后,使得在发生错误时自动重定向到淘宝错误页,所以即使在业务接口做了跨域处理,前端仍会出现跨域问题,因为这一次跨到了淘宝错误页的域。

nginx 配置目录在 /home/admin/cai/conf

配置文件中并未出现重定向页面,重定向页面的配置在另一个文件中 /opt/taobao/tengine/conf/services.conf

代码语言:javascript
复制

重新加载配置 /home/admin/cai/bin/nginxctl reload

至此,刨根问底结束。

总结一下解决方案

方案 1:关闭 nginx 的 proxy_intercept_errors 选项

proxy_intercept_errors 指令后面跟 onoff ,分别表示 打开 或 关闭 拦截错误页功能。

方案 2:避免在请求时直接产生错误,在本例中是请求参数缺失的问题

@RequestParam 注解默认是必传的,如果没有会报 400 错误,所以才会重定向到淘宝错误页。

更改为 @RequestParam(required = false)

然后在接下来的业务逻辑中进行校验

代码语言:javascript
复制

实验验证

有了合理的猜想后,我们需要来验证一下,到底是不是上述猜想导致的,因此需要验证一下。

验证:修改 nginx 的 proxy_intercept_errors 配置选项,将拦截关闭

预期效果:不会重定向,且出现原生的 tomcat 错误页面

实验后:

控制台 fetch 时也不会出现跨域错误了

至此,问题解决完成。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-08-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 01二进制 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 事件起因
  • 什么是跨域
  • CORS 工作机制
  • Spring 中对于跨域的常见解决方案
  • 上述方案使用结束之后仍然失效?
  • 治标 or 治本
  • 刨根问底一下
  • 总结一下解决方案
  • 实验验证
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档