专栏名称: Java知音
专注于Java,推送技术文章,热门开源项目等。致力打造一个有实用,有情怀的Java技术公众号!
目录
相关文章推荐
碳索储能  ·  300MW/600MWh!清远市粤新能源科技 ... ·  3 天前  
碳索储能  ·  300MW/600MWh!清远市粤新能源科技 ... ·  3 天前  
东方电气  ·  东方e闻 | 获批国家重点研发计划专项 ·  4 天前  
东方电气  ·  东方e闻 | 获批国家重点研发计划专项 ·  4 天前  
嘶吼专业版  ·  【急聘】京东集团信息安全部招人啦~~ ·  5 天前  
财联社AI daily  ·  微盟集团回应腾讯连续减持! ·  5 天前  
财联社AI daily  ·  微盟集团回应腾讯连续减持! ·  5 天前  
51好读  ›  专栏  ›  Java知音

SpringBoot CORS 配置详解:允许跨域请求的最佳实践

Java知音  · 公众号  · 互联网安全  · 2025-01-09 10:05

主要观点总结

本文介绍了跨域请求的背景、重要性、概念、跨域的情形、预检请求、跨域提示以及如何处理跨域问题。同时提供了解决跨域问题的后端代码和JS测试代码。

关键观点总结

关键观点1: 跨域请求的背景和重要性

在现代Web开发中,跨域请求是一个常见且重要的概念。涉及多个前端和后端服务时,跨域问题会对应用和用户体验造成影响。

关键观点2: 跨域请求的概念和跨域的情形

跨域请求是由于浏览器的同源政策导致的,不同源的网页之间进行交互时会遇到的限制。源包括协议、域名和端口。跨域情形包括协议、端口或域名变化的情况。

关键观点3: 预检请求(Preflight Request)

预检请求是CORS(跨源资源共享)机制中的一部分,用于在发送复杂的跨域请求之前,先向服务器发送一个HTTP OPTIONS请求,以确认服务器是否允许实际的请求。

关键观点4: 如何处理跨域问题

后端可以通过配置CORS来解决跨域问题。允许或不允许跨域请求完全取决于程序员的设置。Springboot允许跨域的后端代码可以通过配置CorsFilter来实现。

关键观点5: 测试跨域问题的JS代码

提供了JS代码示例,用于测试跨域问题。通过在浏览器控制台执行该代码,可以模拟发送跨域请求。


正文

跨域请求的背景和重要性

在现代 Web 开发中,跨域请求是一个常见且重要的概念。随着互联网应用的日益复杂,尤其是在涉及多个前端和后端服务的情况下,跨域问题经常会对应用的功能和用户体验造成影响。

背景

开发项目时,遇到一个需求,当时项目配套的线下商铺已由物业交付给我司运营,运营团队需要招商引资,经过公司考虑决定开发一个公众号交给运营团队,以便客户能够在线选择商铺。

公众号内嵌一个H5页面,做到用户点击平面图中单元格(每一个商铺号就是一个单元格)优先锁定商铺,由于商铺分布在多个区域,UI绘图也需要时间,再加需求紧迫,项目必须在三天内上线。

前端团队采用了 Canvas 技术,让用户能够直观地选择商铺单元格,并填写提交个人资料。开发过程中,前后端进行了接口联调,在测试环境中没有明显的问题。然而,当项目部署到微信公众号后,出现了跨域请求问题,直接是空白页面。

当时,前端因为配置代理的进度缓慢,跨域配置的解决方案转到了后端。这一问题突显了跨域请求在 Web 开发中的重要性,特别是在需要与多个服务进行交互时。

跨域请求的重要性

  • 安全性: 浏览器的同源政策旨在保护用户,防止恶意网站窃取信息。跨域请求需要经过严格的检查和配置,以确保数据传输的安全性。

  • 用户体验: 跨域请求的限制可能会导致用户在操作过程中遇到障碍,影响应用的流畅性和可用性。在我们的项目中,如果不及时解决跨域问题,将会直接影响客户体验和业务进展。

  • 业务需求: 在某些情况下,业务需求可能需要不同来源的资源交互。例如,在我们开发的微信公众号中,需要与后端服务进行数据交互,以完成用户的选择和定金缴纳等操作。

  • 快速迭代: 随着项目的推进,及时处理跨域问题是确保项目快速上线的重要环节。在短时间内解决跨域配置,能够为后续的功能扩展和业务发展打下良好的基础。

什么是跨域

跨域是指在 Web 应用中,由于浏览器的同源政策(Same-Origin Policy),不同源的网页之间进行交互时所遇到的限制。源的定义包括三个部分:协议(如 http 或 https)、域名(如 example.com)和端口(如 80 或 443)。只有当这三者都相同的时候,两个 URL 被认为是同源的。

为什么有同源政策?

通俗来说,浏览器厂商开发出来的浏览器都是有做安全限制的,当你打开某个网站时,浏览器就已经将请求标头中的origin属性改成了当前网站的域名。例如我访问bilibili,会是这样的一个origin,你在当前页面中做以下几种操作,均会出现跨域:

跨域的情形

1,http://www.bilibili.com(假设存在)

2,https://www.bilibili.com:8086(假设存在)

3,http://admin.www.bilibili.com(假设存在)

跨域原因解释

情况1跨域的原因是scheme(标识特定协议或资源类型的字符串)变了

情况2 跨域的原因是port(端口号)变了

情况3跨域的原因是host(域名,admin.www.bilibili.com是域名)变了

如何证明上述情况就是跨域?

跨不跨域框架说了算,来看看Springboot框架是如何认定为跨域的,先附上截图,然后给源码解释

处理请求相关的参数,并通过比较来判断是否跨域的源码

package org.springframework.web.cors;
public abstract class CorsUtils {
    public CorsUtils() {
    }
 
//方法名就直接体现了方法的作用,判断是否是跨域请求
public static boolean isCorsRequest(HttpServletRequest request) {
        String origin = request.getHeader("Origin");
        if (origin == null) {
            return false;
        } else {
            UriComponents originUrl = UriComponentsBuilder.fromOriginHeader(origin).build();
            String scheme = request.getScheme();
            String host = request.getServerName();
            int port = request.getServerPort();
//上面的代码是从请求体中获取协议,域名,端口的value值,拿到这些值就是为了和Origin作比较
//通过截图也能看到Origin包含了scheme,host,port,他们分别是https,www.bilibili.com,443
            return !ObjectUtils.nullSafeEquals(scheme, originUrl.getScheme())
 || !ObjectUtils.nullSafeEquals(host, originUrl.getHost())
 || getPort(scheme, port) != getPort(originUrl.getScheme(), originUrl.getPort());
        }
    }
}

源码中不难看出来,在经过一番处理之后,会通过客户端传递的Origin中的信息和接口服务资源做协议,端口,域名的比对,只要有一处不一样那就是跨域,框架会告知浏览器跨域,具体的比对过程并不难,我已经贴出来了包名和类名,鼓励朋友们自己动手。

为什么是这样,而不是那样

既然服务器有处理请求,为什么你在浏览器上看不到响应回来的HTTP状态码,服务器应该要给客户端返回个状态码,取而代之的却是显示:此请求没有发起程序请求或者类似的其他提示,这都要归功于预检请求,也是浏览器厂商默认遵循的一个标准规范,属于 CORS(跨源资源共享)机制的一部分。

跨域提示截图

或者

预检请求

预检请求(Preflight Request)是 CORS(跨源资源共享)机制中的一个重要概念,用于在发送复杂的跨域请求之前,先向服务器发送一个 HTTP OPTIONS 请求,以确认服务器是否允许实际的请求。预检请求的目的是为了增强安全性,确保客户端在发送敏感数据时得到服务器的许可。

何时触发预检请求

预检请求通常在以下情况下触发:

复杂请求:
  • 当使用的 HTTP 方法不是简单请求中的 GETPOST(如 PUTDELETE)。

  • 当请求中包含自定义头部(例如,X-Custom-Header)。

  • 当 Content-Type 的值不是简单请求允许的类型(如 application/x-www-form-urlencodedmultipart/form-datatext/plain)。

服务器端的 CORS 配置:
  • 只有在服务器配置了 CORS,并明确允许来自特定源的请求时,预检请求才会返回成功。

预检请求关服务器什么事情

完全不瞎说,有没有预检请求,依旧是springboot框架说了算,先附上原图,在附上部分源码

当我从知乎页面上请求我本机的服务接口时

服务器处理预检请求

首先服务器确实收到了该次请求,截图如下:

处理预检请求的截图:

OPTIONS请求就是预检请求的请求方式,这里解释不了为什么,只能回答这就是规范

处理预检请求的源码:

 public static boolean isPreFlightRequest(HttpServletRequest request) {
        //先判断是不是OPTIONS请求,若是,则表示是预检请求
        return HttpMethod.OPTIONS.matches(request.getMethod())
        //预检请求时,http请求头一定要给Origin
         && request.getHeader("Origin") != null 
        //预检请求时,会给定名为Access-Control-Request-Method的请求头
         && request.getHeader("Access-Control-Request-Method") != null;
    }

服务器如何处理跨域呢,允许还是不允许?

允许还是不允许,完全看程序员如何设置跨域规则,跨域策略,不做深入讲解,但是教你如何避开雷区,先看看核心逻辑的截图

服务器会判断当前是否是预检请求,如果是,则会调用一个处理内部请求的方法,如图

关键点:allowOrigin为什么为null,checkOrigin方法到底做了什么比较

知识点回顾

问题到这里很清晰了,当程序执行到ObjectUtils.isEmpty(this.allowedOrigins)或者this.allowedOrigins.contains("*"),if语句的条件不成立了,因为this.allowedOrigins并不包含客户端的域名,也就是例子中的https://www.bilbili.com或者https://www.zhihu.com,我们要处理的正是allowedOrigins

private List allowedOrigins;

他是以数组的形式被持有的,有很多个API可以给这个数组初始化值,在我的代码中,只展示一种,因为我们要学的不是API,而是发现问题,拆分问题,解决问题的心法,API什么的不重要。

以上介绍了什么是跨域,跨域的情形,以及预检请求作为web浏览器的规范,以及服务器如何处理预检请求,浏览器对于未通过的预检请求会以什么形式展示给用户,接下来告诉大家如何解决这种小小的问题~

springboot解决跨域的方式非常之多,但是从最底层解决,往往能学到更多指定问题之外的知识

SpringBoot允许跨域的后端代码

@Configuration
public class CorsConfig {
 
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        //config.setAllowCredentials(true); // 允许发送凭据,雷区
        config.addAllowedOrigin("*"); //允许任意域名跨域访问接口
        config.addAllowedHeader("*"); // 允许所有头部信息
        config.addAllowedMethod("*"); // 允许所有请求方法
        source.registerCorsConfiguration("/**", config); // 应用于所有路径
        return new CorsFilter(source);
    }
}

这段配置足已解决前端跨域问题,之前说的雷区就是允许发送凭据的代码和config.addAllowedOrigin("*");不可以一起使用,否则会报错。到这里,一切OK,前端跨域的问题已经解决~

给大家一段便捷的JS代码用来测试跨域问题,JS代码不做解释,相信看懂不成问题

模拟跨域的JS代码

var xhr = new XMLHttpRequest();
xhr.open('post''http://localhost:8081/admin/captcha/v1/generateCaptcha');
xhr.setRequestHeader('Content-Type''application/json'); // 设置请求头
xhr.setRequestHeader('authracation''abcdefghijklmnopqrstuvwxyz'); // 设置请求头
xhr.onload = function(e{
    var xhr = e.target;
    console.log(xhr.responseText);
};
 
xhr.send('{}');

这段js代码,按F12,在浏览器的控制台中直接执行,支持IE和Google浏览器,亲测有效,需要根据实际的请求进行微调,不要在你自己的WEB项目或者API文档页面打开,否则无法达到测试跨域的效果,具体原因,我相信你理解了上面的知识点之后应该能明白。解决问题的代码很少,但是知识点并不少,留心处处皆学问哈

作者:baby bear 竟陵
来源:blog.csdn.net/Ta20220617/article/details/143473836

1. Java面试题精选阶段汇总,已更新450期~

2. 推荐一款精美、高质量、开源的问卷系统

3. 一款高颜值、开源的物联网一体化平台

4. 18 个一线工作中常用 Shell 脚本【实用版】

PS:因公众号平台更改了推送规则,如果不想错过内容,记得读完点一下在看,加个星标,这样每次新文章推送才会第一时间出现在你的订阅列表里。

“在看”支持我们,共同成长