Springboot解决跨域问题

时间:2025-02-17 18:37:01

前言

        跨域问题是浏览器为了保护用户的信息安全,实施了同源策略(Same-Origin Policy),即只允许页面请求同源(相同协议、域名和端口)的资源,当 JavaScript 发起的请求跨越了同源策略,即请求的目标与当前页面的域名、端口、协议不一致时,浏览器会阻止请求的发送或接收。
        同源策略/SOP(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,现在所有支持 JavaScript 的浏览器都会使用这个策略。如果缺少了同源策略,浏览器很容易受到 XSS、 CSFR 等攻击。

同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个 ip 地址,也非同源。

   跨域主要涉及4个响应头

  • Access-Control-Allow-Origin 用于设置允许跨域请求源地址 (预检请求和正式请求在跨域时候都会验证)

  • Access-Control-Allow-Headers 跨域允许携带的特殊头信息字段 (只在预检请求验证)

  • Access-Control-Allow-Methods 跨域允许的请求方法或者说HTTP动词 (只在预检请求验证)

  • Access-Control-Allow-Credentials 是否允许跨域使用cookies,如果要跨域使用cookies,可以添加上此请求响应头,值设为true(设置或者不设置,都不会影响请求发送,只会影响在跨域时候是否要携带cookies,但是如果设置,预检请求和正式请求都需要设置)。不过不建议跨域使用(项目中用到过,不过不稳定,有些浏览器带不过去),除非必要,因为有很多方案可以代替。

1)SpringBoot解决跨域

1.使用 @CrossOrigin 注解实现跨域【局域类跨域】

 此注解既可以修饰类,也可以修饰方法。当修饰类时,表示此类中的所有接口都可以跨域;当修饰方法时,表示此方法可以跨域

@CrossOrigin(origins = "*") //origins可以指定请求来源,*代表全部
2.通过配置文件实现跨域【全局跨域】

#yml配置
open:
  allow:
    origin: '*'

import .slf4j.Slf4j;
import .;
import ;
import ;
import ;
import ;
import ;
import ;
import ;

@Slf4j
@Configuration
public class CorsConfig {

    @Value("${:*}")
    private String allowOrigin;

    @Bean
    public CorsFilter corsFilter() {
        //
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        //1,允许任何来源
        corsConfiguration
            .setAllowedOriginPatterns(((allowOrigin, ",")));
        //2,允许任何请求头
        ();
        //3,允许任何方法
        ();
        //4,允许凭证
        (true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        ("/**", corsConfiguration);
        return new CorsFilter(source);
    }
}


#yml配置
open:
  allow:
    origin: ,


@Slf4j
@Configuration
@Profile("prod")
public class CorsConfig {

    @Value("${}")
    private String allowOrigin;

    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        for (String r : (",")) {
            (r);
        }
        ("*");
        ();
        ();
        (true);
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        ("/**", buildConfig());
        return new CorsFilter(source);
    }
}



3.通过 CorsFilter 对象实现跨域【全局跨域】

import ;
import ;
import ;
import ;
import ;
 
@Configuration
public class MyCorsFilter {
    @Bean
    public CorsFilter corsFilter() {
        // 创建 CORS 配置对象
        CorsConfiguration config = new CorsConfiguration();
        // 支持域
        ("*");
        // 是否发送 Cookie
        (true);
        // 支持请求方式
        ("*");
        // 允许的原始请求头部信息
        ("*");
        // 暴露的头部信息
        ("*");
        // 添加地址映射
        UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
        ("/**", config);
        // 返回 CorsFilter 对象
        return new CorsFilter(corsConfigurationSource);
    }
}


4.通过 Response 对象实现跨域【局域方法跨域】

import ;
import ;
import ;
import ;
@RestController
public class TestController {
    @RequestMapping("/test")
    public Result<Object> test(HttpServletResponse response) {
   
        ("Access-Control-Allow-Origin", "*");
        return (new HashMap<String, Object>() {{
            put("state", 200);
            put("data", "跨域访问");
            put("msg", ""));
        }};
    }
}


5.通过实现 ResponseBodyAdvice 实现跨域【全局跨域】

import ;
import ;
import ;
import ;
import ;
import ;
 
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
 
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class selectedConverterType, ServerHttpRequest request,
                                  ServerHttpResponse response) {

        ().set("Access-Control-Allow-Origin", "*");
        return body;
    }
}
 2)Nginx解决跨越
server {
    listen       443 ssl;
    server_name  ;
    ssl_certificate  /usr/local/nginx/conf/cert/;
    ssl_certificate_key /usr/local/nginx/conf/cert/;
    location /api/ {
        # 允许跨域请求的域名,* 表示允许所有域名访问
        add_header 'Access-Control-Allow-Origin' '*';
 
        # 允许跨域请求的方法
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
 
        # 允许跨域请求的自定义 Header
        add_header 'Access-Control-Allow-Headers' 'Origin, X-Requested-With, Content-Type, Accept';
 
        # 允许跨域请求的 Credential,如果需要传递Cookie就需要开启此项
        add_header 'Access-Control-Allow-Credentials' 'true';
 
        # 预检请求的存活时间,即 Options 请求的响应缓存时间
        add_header 'Access-Control-Max-Age' 3600;
 
        # 处理预检请求
        if ($request_method = 'OPTIONS') {
            return 204;
        }
        proxy_pass http://127.0.0.1:8080/;
    }
    
server {
    listen 80;
    server_name ;
        #add_header Access-Control-Allow-Origin '*';
        #add_header Access-Control-Allow-Methods '*';
        #add_header Access-Control-Allow-Headers '*';
        #add_header Access-Control-Allow-Credentials 'true';
    rewrite ^(.*)$ https://${server_name}$1 permanent;
}

}

注意:nginx与springboot跨域配置留一个即可

3)网关解决跨域

Spring Cloud Gateway 中解决跨域问题可以通过以下方式实现 

1.gateway设置允许跨域

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedHeaders: "*"
            allowedMethods: "*"
        default-filters:
          - DedupeResponseHeader=Vary Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST

2.手动写一个 CorsResponseHeaderFilter 的 GlobalFilter 去修改Response中的头 


import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;

import ;
import ;
import ;

@Component
public class CorsResponseHeaderFilter implements GlobalFilter, Ordered {

    private static final String ANY = "*";

    @Override
    public int getOrder() {
        // 指定此过滤器位于NettyWriteResponseFilter之后
        // 即待处理完响应体后接着处理响应头
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
    }

    @Override
    @SuppressWarnings("serial")
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return (exchange).then((() -> {
            ().getHeaders().entrySet().stream()
                    .filter(kv -> (() != null && ().size() > 1))
                    .filter(kv -> (().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)
                            || ().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)
                            || ().equals()))
                    .forEach(kv ->
                    {
                        // Vary只需要去重即可
                        if(().equals())
                            (().stream().distinct().collect(()));
                        else{
                            List<String> value = new ArrayList<>();
                            if(().contains(ANY)){  //如果包含*,则取*
                                (ANY);
                                (value);
                            }else{
                                (().get(0)); // 否则默认取第一个
                                (value);
                            }
                        }
                    });
        }));
    }
}
4) 使用代理

在Vue的开发环境中,可以在文件中配置代理,将API请求代理到后端服务器上,从而避免跨域问题。

 = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080', // 后端服务实际地址
        changeOrigin: true, // 是否改变域名
        pathRewrite: {
          '^/api': '' // 路径重写,将前缀/api转为/
        }
      }
    }
  }
}
5)JSONP

JSONP(JSON with Padding)是另一种解决跨域的方法,但它只支持GET请求。Spring Boot可以配置JSONP的响应,而前端需要改写发送请求的方式。

6)中间件

可以在中创建一个中间件来转发请求,这样前端请求首先发送到服务器,服务器再将请求转发到后端API,以此绕过浏览器的同源策略限制。

注意事项
  • 在开发环境中解决跨域问题时,要注意不要将敏感信息(如Token)暴露给不安全的跨域请求。
  • 在生产环境中,推荐使用Nginx反向代理或其他服务器端解决方案,以提高安全性。