Cors跨域(一):深入理解跨域请求概念及其根因

前言

你好,我是YourBatman。

成都创新互联公司专注于郫都企业网站建设,响应式网站设计,商城网站定制开发。郫都网站建设公司,为郫都等地区提供建站服务。全流程按需定制,专业设计,全程项目跟踪,成都创新互联公司专业和态度为您提供的服务

做Web开发的小伙伴对“跨域”定并不陌生,像狗皮膏药一样粘着几乎每位同学,对它可谓既爱又恨。跨域请求之于创业、小型公司来讲是个头疼的问题,因为这类企业还未沉淀出一套行之有效的、统一的解决方案。

让人担忧的是,据我了解不少程序员同学(不乏有高级开发)碰到跨域问题大都一头雾水:

然后很自然的 用谷歌去百度一下搜索答案,但相关文章可能参差不齐、鱼龙混杂。短则半天长则一天(包含改代码、部署等流程)此问题才得以解决,一个“小小跨域”问题成功偷走你的宝贵时间。

既然跨域是个如此常见(特别是当下前后端分离的开发模式),因此深入理解CORS变得就异常的重要了(反倒前端工程师不用太了解),因此早在2019年我刚开始写博客那会就有过较为详细的系列文章:

现在把它搬到公众号形成技术专栏,并且加点料,让它更深、更全面、更系统的帮助到你,希望可以助你从此不再怕Cors跨域资源共享问题。

本文提纲

版本约定

  • JDK:8
  • Servlet:4.x

正文

文章遵循一贯的风格,本文将采用概念 + 代码示例的方式,层层递进的进行展开叙述。那么上菜,先来个示例预览,模拟一下跨域请求,后面的一些的概念示例将以此作为抓手。

模拟跨域请求

要模拟跨域请求的根本是需要两个源:让请求的来源和目标源不一样。这里我就使用IDEA作为静态Web服务器(63342),Tomcat作为后端动态Servlet服务器(8080)。

说明:服务器都在本机,端口不一样即可

前端代码

 
 
 
 
  1.     
  2.     CORS跨域请求
  3.     
  4.     
  5. 跨域从服务端获取内容
  •  使用IDEA作为静态web服务器,浏览器输入地址即可访问(注:端口号为63342):

    后端代码

    后端写个Servlet来接收cors请求

     
     
     
     
    1. /**
    2.  * 在此处添加备注信息
    3.  *
    4.  * @author YourBatman. Send email to me
    5.  * @site https://yourbatman.cn
    6.  * @date 2021/6/9 10:36
    7.  * @since 0.0.1
    8.  */
    9. @Slf4j
    10. @WebServlet(urlPatterns = "/cors")
    11. public class CorsServlet extends HttpServlet {
    12.     @Override
    13.     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    14.         String requestURI = req.getRequestURI();
    15.         String method = req.getMethod();
    16.         String originHeader = req.getHeader("Origin");
    17.         log.info("收到请求:{},方法:{}, Origin头:{}", requestURI, method, originHeader);
    18.         resp.getWriter().write("hello cors...");
    19.     }
    20. }

    启动后端服务器,点击页面上的按钮,结果如下:

    服务端控制台输出:

     
     
     
     
    1. ... INFO  c.y.cors.servlet.CorsServlet - 收到请求:/cors,方法:GET, Origin头:http://localhost:63342

    服务端输出日志,说明即使前端的Http Status是error,但服务端还是收到并处理了这个请求的下面以此代码示例为基础,普及一下和Cors跨域资源共享相关的概念。

    Host、Referer、Origin的区别

    这哥三看起来很是相似,下面对概念作出区分。

    1.Host:去哪里。域名+端口。值为客户端将要访问的远程主机,浏览器在发送Http请求时会带有此Header

    2.Referer:来自哪里。协议+域名+端口+路径+参数。当前请求的来源页面的地址,服务端一般使用 Referer 首部识别访问来源,可能会以此进行统计分析、日志记录以及缓存优化等

    常见应用场景:百度的搜索广告就会分析Referer来判断打开站点是从百度搜索跳转的,还是直接URL输入地址的

    一般情况下浏览器会带有此Header,但这些case不会带有Referer这个头

    3.Origin:来自哪里(跨域)。协议+域名+端口。它用于Cors请求和同域POST请求

    可以看到Referer与Origin功能相似,前者一般用于统计和阻止盗链,后者用于CORS请求。但是还是有几点不同:

    1.只有跨域请求,或者同域时发送post请求,才会携带Origin请求头;而Referer只要浏览器能获取到都会携带(除了上面说明的几种case外)

    2.若浏览器不能获取到请求源页面地址(如上面的几种case),Referer头不会发送,但Origin依旧会发送,只是值是null而已(注:虽然值为null,但此请求依旧属于Cors请求哦),如下图所示:

    3.Origin的值只包括协议、域名和端口,而Rerferer不但包括协议、域名、端口还包括路径,参数,注意不包括hash值

    浏览器的同源策略

    浏览器的职责是展示/渲染document、css、script脚本等,但是这些资源(将document、css、script统一称为资源)可能来自不同的地方,如本地、远程服务器、甚至黑客的服务器......浏览器作为万维网的入口,是我们接入互联网最重要的软件之一(甚至没有之一),因此它的安全性显得尤为重要,这就出现了浏览器的同源策略。

    同源策略是浏览器一个重要的安全策略,它用于限制一个origin源的document或者它加载的脚本如何能与另一个origin源的资源进行交互。它能帮助阻隔恶意文档,减少(并不是杜绝)可能被攻击的媒介。

    方便和安全往往是相悖的:安全性增高了,方便性就会有所降低那么问题来了,什么才算同源?

    同源的定义

    URL被称作:统一资源定位符,同源是针对URL而言的。一个完整的URL各部分如下图所示:

    Tips:域名和host是等同的概念,域名+端口号 = host+端口号(大部分情况下你看到域名并没有端口号,那是采用了默认端口号80而已)同源:只和上图的前两部分(protocol + domain)有关,规则为:全部相同则为同源。这个定义不难理解,但有几点需要再强调一下:

    当然也有说三部分的(协议+host+port),理解其含义就成

    下面通过举例来彻底了解下。譬如,我的源URL为:http://www.baidu.com/api/user,下面表格描述了不同URL的各类情况:

    不同源的网络访问

    浏览器同源策略的存在,限制了不同源之间的交互,实为不便。但是浏览器也开了一些“绿灯”,让其不受同源策略的约束。此种情况一般可分为如下三类:

    1.跨域写操作(Cross-origin writes):一般是被允许的。如链接(如a标签)、重定向以及表单提交(如form表单的提交)

    2.跨域资源嵌入(Cross-origin embedding):一般是允许的。比如下面这些例子: