跨域问题报错信息:
Referrer Policy:strict-origin-when-cross-origin
跨域问题是在前后端分离的情况下一个非常常见的问题,通常添加一个跨域拦截器就可以解决,但是在后台添加后还是出现跨域问题,到底是配置错误还是哪里出了问题?
本文将基于 SpringBoot 2.7 从多种角度解决跨域问题。
核心跨域拦截代码
/**
* 跨域处理
*
* @param registry
*/
@Override
public 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 {
@Bean
WebInterceptor webInterceptor() {
return new WebInterceptor();
}
/**
* 添加拦截器
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(webInterceptor())
.addPathPatterns("/**")
.excludePathPatterns(TokenConst.NO_TOKEN_API);
}
/**
* 资源过滤
*
* @param registry
*/
@Override
public 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
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedMethods("GET","HEAD","POST","PUT","PATCH","DELETE","OPTIONS","TRACE")
.allowedOriginPatterns("*")
.allowedHeaders("*")
.maxAge(3600);
}
}
其中 WebInterceptor
主要用于做 Token 鉴权拦截器,这里不在拓展。
Nginx 允许跨域的配置如下:
# HTTPS server
server {
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 中配置允许跨域就可以视线跨域访问了,使用效果是一样的
如果 Nginx 配置了允许跨域请求,同时 SpringBoot 也添加了跨域拦截器,那会怎么样?是不是变得更强了?
实际测试的效果是不能跨域了。
浏览器报错如下:
看后台接口日志是请求已经有返回了,但是前端还是提示跨域异常了(CROS error
)
点击具体的请求可以看到以下信息:
请求状态码是 200,表明服务器请求通了,下边提示
Referrer Policy:strict-origin-when-cross-origin
这表明当前的请求是跨域请求
下边是关键点,返回的头里边包含了两个 access-control-allow-origin
header,其中一个是 *
也就是允许跨域,另一个是当前请求的域名(非SpringBoot后台项目服务的域名),这还有正正的负的道理?
解决办法也很简单,要么删掉 Nginx 的跨域配置,要么删掉 SpringBoot 项目中的跨域拦截器。
这里作者推荐删除 Nginx 的跨域配置,因为如果项目迁移,Nginx 配置不属于项目代码,到时候出现明明好好的代码却跑不通了,那就是写代码的人的问题了。
至此,一个浏览器跨域导致的生产事故大案已经完美告破!!!
springboot和springframework版本依赖关系
Spring Boot Starter Test Maven 仓库依赖
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