Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >跨域的解决方式(java后端)

跨域的解决方式(java后端)

作者头像
冬天vs不冷
发布于 2025-01-21 02:06:27
发布于 2025-01-21 02:06:27
27400
代码可运行
举报
文章被收录于专栏:springbootspringboot
运行总次数:0
代码可运行

一、跨域介绍

1、什么是跨域

  • 同源策略(Sameoriginpolicy)是浏览器最核心也最基本的安全功能
  • 一个页面发起的ajax请求,只能是与当前页域名相同的路径,这能有效的阻止跨站攻击
  • 所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号

2、为什么会产生跨域

  • 前后端分离模式下,客户端请求前端服务器获取视图资源
  • 客户端向后端服务器获取数据资源
  • 前端服务器的协议,IP和端口和后端服务器不一样,就产生了跨域

3、禁止跨域的原因

跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。你可能会疑问明明通过表单的方式可以发起跨域请求,为什么Ajax就不会?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。

二、简单请求和非简单请求

  • CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)
  • 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制
  • 浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)

1、简单请求

1.1、什么时简单请求

只要同时满足以下两大条件,就属于简单请求

  • 请求方法是以下三种方法之一
    • HEAD
    • GET
    • POST
  • HTTP的头信息不超出以下几种字段
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type:只限制三个值
      • application/x-www-form-urlencoded
      • multipart/form-data
      • text/plain

结合日常开发总结一句话:一般get请求就是简单请求,带json请求体的post请求就是非简单请求

1.2、简单请求基础流程
  • 浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段
  • Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求

举例

  • HTTP请求的方法是GET,响应response添加自定义头信息X-Name
  • 下面是这个请求的HTTP头信息
  • 如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应
    • 浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文)
    • 从而抛出一个错误,但是这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200
  • 如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段
    • 有四个与CORS请求相关的字段,都以Access-Control-开头
  1. Access-Control-Allow-Origin
    • 该字段是必须的
    • 要么是请求时Origin字段的值
    • 要么是一个*,表示接受任意域名的请求
  2. Access-Control-Allow-Credentials
    • 该字段可选
    • 它的值是一个布尔值,表示是否允许发送Cookie
    • 默认情况下,Cookie不包括在CORS请求之中
    • 设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器
    • 另外一方面,前端AJAX请求必须设置withCredentials = true
    • 注意:如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名
  3. Access-Control-Expose-Headers
    • 该字段可选
    • CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma
    • 前端响应如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定

简单请求响应跨域设置

前端

后端

2、非简单请求

2.1、预检请求
  • 非简单请求是那种对服务器有特殊要求的请求
    • 比如请求方法是PUT或DELETE
    • 或者Content-Type字段的类型是application/json
  • 非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求
    • 浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段
    • 只有得到肯定答复,浏览器才会发出正式的请求,否则就报错

举例

  • HTTP请求的方法是POST,携带json请求体并添加自定义头信息X-Data
  • 下面是这个"预检"请求的HTTP头信息
  • "预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源
  • Access-Control-Request-Method
    • 该字段是必须的
    • 用来列出浏览器的CORS请求会用到哪些HTTP方法
  • Access-Control-Request-Headers
    • 该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段
2.2、预检请求的回应
  • 如果服务器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段
    • 浏览器就会认定,服务器不同意预检请求
    • 控制台会打印出如下的报错信息
  • 如果服务器收到"预检"请求以后,检查了OriginAccess-Control-Request-MethodAccess-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应
  • 关键的还是Access-Control-Allow-Origin字段
    • 表示http://localhost:8081可以请求数据
    • 该字段也可以设为星号,表示同意任意跨源请求
  • Access-Control-Allow-Methods
    • 该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法
    • 注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求
  • Access-Control-Max-Age
    • 该字段可选,用来指定本次预检请求的有效期,单位为秒
    • 在此期间,不用发出另一条预检请求
  • Access-Control-Allow-Headers
    • 该字段可选,默认情况只有几个固定请求头可以发送
    • 如果前端需要发送其他请求头,就必须在Access-Control-Allow-Headers里面指定
2.3、浏览器的正常请求和回应
  • 一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段
  • 服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段

"预检"请求之后,浏览器的正常CORS请求

  • 头信息的Origin字段是浏览器自动添加的

服务器正常的回应

  • Access-Control-Allow-Origin字段是每次回应都必定包含的

非简单请求响应跨域设置

前端

后端

3、自定义跨域过滤器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
public class CrosFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest request =(HttpServletRequest) servletRequest;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "*");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "*");
        /**
         * 如果设置Access-Control-Allow-Credentials,允许跨域请求携带cookie
         * 那么Access-Control-Allow-Origin不能为*,只能为指定的域名
         */
        // response.setHeader("Access-Control-Allow-Credentials", "true");
        // 非预检请求,放行即可,预检请求,则到此结束,不需要放行
        if (!"OPTIONS".equalsIgnoreCase(request.getMethod())) {
            filterChain.doFilter(servletRequest, servletResponse);
        }
    }
}

三、解决方式

1、@CrossOrigin注解

@CrossOrigin源码

  • @CrossOrigin可以用在被RequestMapping修饰的方法Controller类上
  • 添加此注解即可实现跨域请求
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {

	@AliasFor("origins")
	String[] value() default {};

	@AliasFor("value")
	String[] origins() default {};

	/**
	 * @since 5.3
	 */
	String[] originPatterns() default {};

	String[] allowedHeaders() default {};

	String[] exposedHeaders() default {};

	RequestMethod[] methods() default {};
	
	String allowCredentials() default "";

	long maxAge() default -1;
}

@CrossOrigin属性介绍

  • origins和value
    • 支持的源,origins和value都是相同的配置,互为别名,默认配置是“*”
    • 表示服务器支持所有源的跨域请求,安全信息较低
    • 最好根据实际情况设置对应的信息(协议 + 域名 + 端口)
  • originPatterns
    • 同样表示支持的源,Spring 5.3 引入的属性,默认为空
    • 与origins二选一,该字段为list,也就是可以配置多个
  • allowedHeaders
    • 允许跨域的请求头信息,默认为“*”表示允许所有的请求头
    • 默认前端可以发送的请求头:Cache-Control、Content-Language、Expires、Last-Modified、Pragma
  • exposedHeaders
    • 服务器允许客户端获取的响应头,默认为空
    • 默认前端可以获取的响应头:Cache-Control、Content-Language、Expires、Last-Modified、Pragm
  • methods
    • 服务器允许的Http Request类型,默认是允许GET、POST、HEAD
  • allowCredentials
    • 浏览器是否需要把凭证(如:cookies、CSRF tokens)发送到服务器,默认是关闭的
    • 该选项开启后会与配置的源建立高度信任的关系,并且还会暴露一些敏感信息,所以开启该选项时origin不允许设置为“*”
  • maxAge
    • “预检”结果的缓存时间,单位是秒
    • 默认1800s,在缓存时间内同一请求不需要“预检”请求

@CrossOrigin注解比较适用于较细粒度的跨域控制

2、CorsRegistry方式

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                //是否发送Cookie
                // .allowCredentials(true)
                //放行哪些原始域
                .allowedOrigins("*")
                .allowedMethods("*")
                .allowedHeaders("*")
                .exposedHeaders("*");
    }
}

3、CorsFilter过滤器

  • 如果设置Access-Control-Allow-Credentials为true,允许跨域请求携带cookie
  • 但是Access-Control-Allow-Origin不能为*,只能为指定的域名
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Configuration
public class GlobalCorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        // 1. 添加 CORS配置信息
        CorsConfiguration config = new CorsConfiguration();
        // 放行哪些原始域
        config.addAllowedOrigin("*");
        // 是否发送 Cookie
        // config.setAllowCredentials(true);
        // 放行哪些请求方式
        config.addAllowedMethod("*");
        // 放行哪些原始请求头部信息
        config.addAllowedHeader("*");
        // 暴露哪些头部信息
        config.addExposedHeader("*");
        // 2. 添加映射路径
        UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
        corsConfigurationSource.registerCorsConfiguration("/**", config);
        // 3. 返回新的CorsFilter
        return new CorsFilter(corsConfigurationSource);
    }
}
3.1、源码解析

CorsFilter过滤器

DefaultCorsProcessor

DefaultCorsProcessor类的handleInternal方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,
		CorsConfiguration config, boolean preFlightRequest) throws IOException {

	String requestOrigin = request.getHeaders().getOrigin();
	// 从配置类CorsConfiguration获取允许的域名
	String allowOrigin = checkOrigin(config, requestOrigin);
	HttpHeaders responseHeaders = response.getHeaders();

	if (allowOrigin == null) {
		logger.debug("Reject: '" + requestOrigin + "' origin is not allowed");
		rejectRequest(response);
		return false;
	}

	// 获取请求的请求方法
	HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);
	// 从配置类CorsConfiguration获取允许的请求方法
	List<HttpMethod> allowMethods = checkMethods(config, requestMethod);
	if (allowMethods == null) {
		logger.debug("Reject: HTTP '" + requestMethod + "' is not allowed");
		rejectRequest(response);
		return false;
	}

	// 获取请求头
	List<String> requestHeaders = getHeadersToUse(request, preFlightRequest);
	// 从配置类CorsConfiguration获取允许的请求头 
	List<String> allowHeaders = checkHeaders(config, requestHeaders);
	if (preFlightRequest && allowHeaders == null) {
		logger.debug("Reject: headers '" + requestHeaders + "' are not allowed");
		rejectRequest(response);
		return false;
	}
	// 设置响应允许域名
	responseHeaders.setAccessControlAllowOrigin(allowOrigin);

	// 如果是预检请求,设置允许请求方法
	if (preFlightRequest) {
		responseHeaders.setAccessControlAllowMethods(allowMethods);
	}

	// 设置允许请求头
	if (preFlightRequest && !allowHeaders.isEmpty()) {
		responseHeaders.setAccessControlAllowHeaders(allowHeaders);
	}

	// 设置允许响应头
	if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
		responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
	}
	
	// 设置是否允许请求携带cookie
	if (Boolean.TRUE.equals(config.getAllowCredentials())) {
		responseHeaders.setAccessControlAllowCredentials(true);
	}
	
	// 设置预检到期时间
	if (preFlightRequest && config.getMaxAge() != null) {
		responseHeaders.setAccessControlMaxAge(config.getMaxAge());
	}

	response.flush();
	return true;
}
  • allowedOrigin:允许域名无论什么情况都会返回
  • 只有预检请求,才有可能设置允许请求方法,请求头,响应头
  • 是否允许携带cookie和到期时间,有则返回

CorsRegistry原理

  • CorsRegistry方式最终也是通过DefaultCorsProcessor类实现
  • 与CorsFilter不同的地方
    • CorsFilter直接将属性添加到CorsConfiguration配置类
    • CorsRegistry则是通过CorsRegistration类set到CorsConfiguration配置类

4、CorsWebFilter网关WebFlux过滤器

  • reactor下的类,非mvc
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Configuration
public class CorsConfigure {
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("*");
        config.addAllowedMethod("*");
        config.addAllowedHeader("*");
        config.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        configSource.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(configSource);
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-04-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
5年经验程序员被问:前后端分离后,如何解决跨域问题?
做Web开发,经常会遇到跨域问题,小伙伴们在面试中,也经常被问到。这不,又有一位工作3年的小伙伴被问到这样一道题,说前后端分离后,如何解决跨域问题?
Tom弹架构
2023/09/07
2.1K0
5年经验程序员被问:前后端分离后,如何解决跨域问题?
SpringBoot使用CORS解决跨域请求问题
同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。 同源策略是浏览器安全的基石。
朝雨忆轻尘
2019/06/19
6.4K0
解决跨域问题的8种方法,含网关、Nginx和SpringBoot~
跨域问题是浏览器为了保护用户的信息安全,实施了同源策略(Same-Origin Policy),即只允许页面请求同源(相同协议、域名和端口)的资源,当 JavaScript 发起的请求跨越了同源策略,即请求的目标与当前页面的域名、端口、协议不一致时,浏览器会阻止请求的发送或接收。
磊哥
2024/01/25
6.2K0
解决跨域问题的8种方法,含网关、Nginx和SpringBoot~
Spring Boot 跨域解决方式
要解释跨域,先要了解同源策略,所谓同源策略就是在浏览器端出于安全考量,向服务端发起请求必须满足:协议相同、Host(ip)相同、端口相同,否则访问将被禁止,该访问也就被称为跨域访问。
数媒派
2022/12/01
6530
跨域问题及CORS解决跨域问题方法
跨域不一定会有跨域问题。因为跨域问题是浏览器对于ajax请求的一种安全限制:一个页面发起的ajax请求,只能是于当前页同域名的路径,这能有效的阻止跨站攻击。
Java架构师必看
2021/03/22
13K0
怎么解决跨域
存在浏览器同源策略,所以才会有跨域问题。那么浏览器是出于何种原因会有跨域的限制呢。其实不难想到,跨域限制主要的目的就是为了用户的上网安全。
程序员子龙
2024/04/30
2100
Spring Boot 解决跨域问题的 3 种方案!
前后端分离大势所趋,跨域问题更是老生常谈,随便用标题去google或百度一下,能搜出一大片解决方案,那么为啥又要写一遍呢,不急往下看。
后端码匠
2021/01/06
3450
CORS跨域问题及解决方案详解
CORS(Cross-Origin Resource Sharing,跨域资源共享)跨域问题源于浏览器的同源策略。同源策略是浏览器的一种安全机制,它要求浏览器在访问一个资源时,该资源的协议、域名和端口必须与当前页面的协议、域名和端口完全一致,否则就会被视为跨域请求,浏览器会对这类请求进行限制。
威哥爱编程
2025/02/25
4330
spring解决跨越问题
而我们刚才是从manage.leyou.com去访问api.leyou.com,这属于二级域名不同,跨域了。
用户4396583
2024/07/25
1340
对象存储COS跨域CORS问题小结
CORS(Cross-origin resource sharing) 中文名称"跨域资源共享",由于安全原因,Web 应用程序默认情况只能在同源(协议、域名和端口)的情况下向服务器获取数据。
吴硕卫
2020/11/19
9.6K0
对象存储COS跨域CORS问题小结
springboot的跨域配置
场景: 在前后端分离协同开发的场景下,跨域是一个非常常见的问题,觉得有必要对这个问题来做一下记录,同时也是强化对这部分知识的学习
在水一方
2022/06/14
8090
springboot的跨域配置
再一次折腾跨域问题
跨域问题在前后端分离的开发场景中经常遇到,回想起来自己也已经折腾了数次,本篇文章主要对跨域问题做个记录和总结。
云原生
2022/03/30
4890
商城项目-跨域问题
而我们刚才是从manage.leyou.com去访问api.leyou.com,这属于二级域名不同,跨域了。
cwl_java
2020/02/11
6260
SpringBoot跨域配置
跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。
小沐沐吖
2022/09/22
1.3K0
SpringBoot跨域配置
前后端分离后,Java Web开发如何解决跨域问题
做Web开发,经常会遇到跨域问题,小伙伴们在面试中,也经常被问到。这不,又有一位工作3年的小伙伴被问到这样一道题,说前后端分离后,如果解决跨域问题。
Tom弹架构
2022/12/19
7890
前后端分离后,Java Web开发如何解决跨域问题
后端工程师需要了解的跨域知识
产品有多端:机构端,局方端 ,家长端等 。每端都有独立的域名,有的是在PC上访问,有的是通过微信公众号来访问,有的是扫码后H5展现。
勇哥java实战
2022/01/14
1K0
后端工程师需要了解的跨域知识
前后端分离项目,如何解决跨域问题
CORS全称Cross-Origin Resource Sharing,意为跨域资源共享。当一个资源去访问另一个不同域名或者同域名不同端口的资源时,就会发出跨域请求。如果此时另一个资源不允许其进行跨域资源访问,那么访问的那个资源就会遇到跨域问题。
macrozheng
2019/08/01
2.4K0
前后端分离项目,如何解决跨域问题
SpringBoot 中实现跨域的5种方式
出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。2021Java面试宝典
程序员白楠楠
2021/02/19
4.2K0
Spring Boot 解决跨域问题的 3 种方案!
前后端分离大势所趋,跨域问题更是老生常谈,随便用标题去google或百度一下,能搜出一大片解决方案,那么为啥又要写一遍呢,不急往下看。
Java技术江湖
2021/01/06
6420
跨域共享CORS详解及Gin配置跨域
跨域简介 当两个域具有相同的协议(如http), 相同的端口(如80),相同的host,那么我们就可以认为它们是相同的域(协议,域名,端口都必须相同)。 跨域就指着协议,域名,端口不一致,出于安全考虑,跨域的资源之间是无法交互的(例如一般情况跨域的JavaScript无法交互,当然有很多解决跨域的方案) 解决跨域几种方案 /* CORS 普通跨域请求:只服务端设置Access-Control-Allow-Origin即可, 前端无须设置,若要带cookie请求:前后端都需要设置。
iginkgo18
2020/12/01
1.8K0
相关推荐
5年经验程序员被问:前后端分离后,如何解决跨域问题?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档