文章目录
- 1摘要
- 2 核心代码
- 2.1 SpringBoot 全局跨域拦截器
- 2.2 Nginx 配置跨域处理
- 2.3 Nginx 和 SpringBoot 同时添加允许跨域处理会怎么样?
- 3 推荐参考资料
1摘要
跨域问题报错信息:
Referrer Policy:strict-origin-when-cross-origin
跨域问题是在前后端分离的情况下一个非常常见的问题,通常添加一个跨域拦截器就可以解决,但是在后台添加后还是出现跨域问题,到底是配置错误还是哪里出了问题?
本文将基于 SpringBoot 2.7 从多种角度解决跨域问题。
2 核心代码
2.1 SpringBoot 全局跨域拦截器
核心跨域拦截代码
/*** 跨域处理** @param registry*/@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowCredentials(true).allowedMethods("GET","HEAD","POST","PUT","PATCH","DELETE","OPTIONS","TRACE").allowedOriginPatterns("*").allowedHeaders("*").maxAge(3600);}
以上为高版本 SpringBoot 的跨域拦截器代码,SpringBoot 版本高于 2.4,SpringBoot 2.7 就用上边的代码。
关于 allowedOriginPatterns
方法说明:
点击 allowedOriginPatterns
方法,打开 Spring 的源码可以看到一下信息:
org.springframework.web.servlet.config.annotation.CorsRegistration#allowedOriginPatterns
/*** Alternative to {@link #allowedOrigins(String...)} that supports more* flexible patterns for specifying the origins for which cross-origin* requests are allowed from a browser. Please, refer to* {@link CorsConfiguration#setAllowedOriginPatterns(List)} for format* details and other considerations.* <p>By default this is not set.* @since 5.3*/public CorsRegistration allowedOriginPatterns(String... patterns) {this.config.setAllowedOriginPatterns(Arrays.asList(patterns));return this;}
源码中一个关键信息点since 5.3
, allowedOriginPatterns
是 Spring 框架 5.3 开始加入的
在 Maven 仓库中依赖信息可以看到 Springboot 2.7.18 使用的 Spring 版本是 5.3.31
仔细排查可发现 SpringBoot 2.4 开始是使用 Spring 5.3 的,因此高于 SpringBoot 2.4 的版本需要使用 allowedOriginPatterns
方法来设置允许跨域来源。低版本允许跨域来源的方法为 allowedOrigins
Maven 仓库查看SpringBoot 中依赖 Spring 的版本信息的地址为:
https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test/2.7.18
如果不使用 allowedOriginPatterns
方法,还是使用旧的方法 allowedOrigins
则会报以下异常:
msg:When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header. To allow credentials to a set of origins, list them explicitly or consider using "allowedOriginPatterns" instead.,trace:org.springframework.web.cors.CorsConfiguration.validateAllowCredentials(CorsConfiguration.java:473)org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:532)org.springframework.web.servlet.DispatcherServlet.getHandler(DispatcherServlet.java:1266)org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1048)org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
从报错信息中也可以看到Spring已经说明需要使用新的方法了。
完整的跨域处理拦截器代码:
package com.ljq.stock.alert.web.interceptor;import com.ljq.stock.alert.common.constant.TokenConst;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;/*** @Description: web 应用配置* @Author: Mr.lu*/
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {@BeanWebInterceptor webInterceptor() {return new WebInterceptor();}/*** 添加拦截器** @param registry*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(webInterceptor()).addPathPatterns("/**").excludePathPatterns(TokenConst.NO_TOKEN_API);}/*** 资源过滤** @param registry*/@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/swagger-ui/**").addResourceLocations("classpath:META-INF/resources/webjars/springfox-swagger-ui/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:META-INF/resources/webjars/");registry.addResourceHandler("/**").addResourceLocations("classpath:META-INF/resources/","classpath:resources/", "classpath:static/","classpath:public/");}/*** 跨域处理** @param registry*/@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowCredentials(true).allowedMethods("GET","HEAD","POST","PUT","PATCH","DELETE","OPTIONS","TRACE").allowedOriginPatterns("*").allowedHeaders("*").maxAge(3600);}}
其中 WebInterceptor
主要用于做 Token 鉴权拦截器,这里不在拓展。
2.2 Nginx 配置跨域处理
Nginx 允许跨域的配置如下:
# HTTPS serverserver {listen 443 ssl;server_name xxx.com;access_log /var/log/nginx/xxx.access.log main;ssl_certificate /etc/ssl/certs/xxx.com.crt;ssl_certificate_key /etc/ssl/certs/xxx.com.key;ssl_session_cache shared:SSL:1m;ssl_session_timeout 5m;ssl_ciphers HIGH:!aNULL:!MD5;ssl_prefer_server_ciphers on;location / {proxy_pass http://127.0.0.1:6666;proxy_set_header Host $host;proxy_set_header X-real-ip $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;add_header Access-Control-Allow-Origin *;add_header Access-Control-Allow-Methods 'GET, POST,PUT, DELETE, OPTIONS';add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection upgrade;proxy_read_timeout 600s;if ($request_method = 'OPTIONS') {return 204;}}}
其中核心跨域配置为:
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST,PUT, DELETE, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
也就是说即时 SpringBoot 项目中不配置跨域拦截器,直接在 Nginx 中配置允许跨域就可以视线跨域访问了,使用效果是一样的
2.3 Nginx 和 SpringBoot 同时添加允许跨域处理会怎么样?
如果 Nginx 配置了允许跨域请求,同时 SpringBoot 也添加了跨域拦截器,那会怎么样?是不是变得更强了?
实际测试的效果是不能跨域了。
浏览器报错如下:
看后台接口日志是请求已经有返回了,但是前端还是提示跨域异常了(CROS error
)
点击具体的请求可以看到以下信息:
请求状态码是 200,表明服务器请求通了,下边提示
Referrer Policy:strict-origin-when-cross-origin
这表明当前的请求是跨域请求
下边是关键点,返回的头里边包含了两个 access-control-allow-origin
header,其中一个是 *
也就是允许跨域,另一个是当前请求的域名(非SpringBoot后台项目服务的域名),这还有正正的负的道理?
解决办法也很简单,要么删掉 Nginx 的跨域配置,要么删掉 SpringBoot 项目中的跨域拦截器。
这里作者推荐删除 Nginx 的跨域配置,因为如果项目迁移,Nginx 配置不属于项目代码,到时候出现明明好好的代码却跑不通了,那就是写代码的人的问题了。
至此,一个浏览器跨域导致的生产事故大案已经完美告破!!!
3 推荐参考资料
springboot和springframework版本依赖关系
Spring Boot Starter Test Maven 仓库依赖
nginx解决跨域和spring解决跨域
SpringBoot解决跨域问题/Nginx中配置解决跨域问题
解决The ‘Access-Control-Allow-Origin‘ header contains multiple values ‘*, *‘, but only one is allowed
SpringBoot 2/3 实现跨域报错:When allowCredentials is true, xxxxxxx , using “allowedOriginPatterns“ instead