Springboot 2.7+解决跨域问题,到底是在SpringBoot中添加拦截器还是修改Nginx配置

文章目录

    • 1摘要
    • [2 核心代码](#2 核心代码)
      • [2.1 SpringBoot 全局跨域拦截器](#2.1 SpringBoot 全局跨域拦截器)
      • [2.2 Nginx 配置跨域处理](#2.2 Nginx 配置跨域处理)
      • [2.3 Nginx 和 SpringBoot 同时添加允许跨域处理会怎么样?](#2.3 Nginx 和 SpringBoot 同时添加允许跨域处理会怎么样?)
    • [3 推荐参考资料](#3 推荐参考资料)

1摘要

跨域问题报错信息:

复制代码
Referrer Policy:strict-origin-when-cross-origin

跨域问题是在前后端分离的情况下一个非常常见的问题,通常添加一个跨域拦截器就可以解决,但是在后台添加后还是出现跨域问题,到底是配置错误还是哪里出了问题?

本文将基于 SpringBoot 2.7 从多种角度解决跨域问题。

2 核心代码

2.1 SpringBoot 全局跨域拦截器

核心跨域拦截代码

java 复制代码
    /**
     * 跨域处理
     *
     * @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
java 复制代码
	/**
	 * 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 则会报以下异常:

java 复制代码
	 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已经说明需要使用新的方法了。

完整的跨域处理拦截器代码:

java 复制代码
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 鉴权拦截器,这里不在拓展。

2.2 Nginx 配置跨域处理

Nginx 允许跨域的配置如下:

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;
            }
        }

    }

其中核心跨域配置为:

nginx 复制代码
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

相关推荐
JosieBook11 分钟前
【SpringBoot】21-Spring Boot中Web页面抽取公共页面的完整实践
前端·spring boot·python
刘一说32 分钟前
Spring Boot+Nacos+MySQL微服务问题排查指南
spring boot·mysql·微服务
叫我阿柒啊4 小时前
从Java全栈到云原生:一场技术深度对话
java·spring boot·docker·微服务·typescript·消息队列·vue3
计算机毕设定制辅导-无忧学长5 小时前
MQTT 与 Java 框架集成:Spring Boot 实战(一)
java·网络·spring boot
叫我阿柒啊5 小时前
从Java全栈到Vue3实战:一次真实面试的深度复盘
java·spring boot·微服务·vue3·响应式编程·前后端分离·restful api
泉城老铁5 小时前
Spring Boot中实现多线程分片下载
java·spring boot·后端
泉城老铁5 小时前
Spring Boot中实现多文件打包下载
spring boot·后端·架构
泉城老铁5 小时前
Spring Boot中实现大文件分片下载和断点续传功能
java·spring boot·后端
友莘居士5 小时前
长流程、复杂业务流程分布式事务管理实战
spring boot·rocketmq·saga·复杂流程分布式事务·长流程
百思可瑞教育6 小时前
Spring Boot 参数校验全攻略:从基础到进阶
运维·服务器·spring boot·后端·百思可瑞教育·北京百思教育