Springboot解决跨域问题

前言

        跨域问题是浏览器为了保护用户的信息安全,实施了同源策略(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 lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Arrays;

@Slf4j
@Configuration
public class CorsConfig {

    @Value("${open.allow.origin:*}")
    private String allowOrigin;

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

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


#yml配置
open:
  allow:
    origin: https://aa.bb.cc,https://mm.nn.hh


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

    @Value("${open.allow.origin}")
    private String allowOrigin;

    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        for (String r : allowOrigin.split(",")) {
            corsConfiguration.addAllowedOrigin(r);
        }
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod(HttpMethod.POST);
        corsConfiguration.addAllowedMethod(HttpMethod.GET);
        corsConfiguration.setAllowCredentials(true);
        return corsConfiguration;
    }

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



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

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


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

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
@RestController
public class TestController {
    @RequestMapping("/test")
    public Result<Object> test(HttpServletResponse response) {
   
        response.setHeader("Access-Control-Allow-Origin", "*");
        return Result.success(new HashMap<String, Object>() {{
            put("state", 200);
            put("data", "跨域访问");
            put("msg", ""));
        }};
    }
}


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

import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
 
@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) {

        response.getHeaders().set("Access-Control-Allow-Origin", "*");
        return body;
    }
}
 2)Nginx解决跨越
server {
    listen       443 ssl;
    server_name  www.aa.cn;
    ssl_certificate  /usr/local/nginx/conf/cert/www.aa.cn.pem;
    ssl_certificate_key /usr/local/nginx/conf/cert/www.aa.cn.key;
    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 www.aa.cn;
        #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 org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@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 chain.filter(exchange).then(Mono.fromRunnable(() -> {
            exchange.getResponse().getHeaders().entrySet().stream()
                    .filter(kv -> (kv.getValue() != null && kv.getValue().size() > 1))
                    .filter(kv -> (kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)
                            || kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)
                            || kv.getKey().equals(HttpHeaders.VARY)))
                    .forEach(kv ->
                    {
                        // Vary只需要去重即可
                        if(kv.getKey().equals(HttpHeaders.VARY))
                            kv.setValue(kv.getValue().stream().distinct().collect(Collectors.toList()));
                        else{
                            List<String> value = new ArrayList<>();
                            if(kv.getValue().contains(ANY)){  //如果包含*,则取*
                                value.add(ANY);
                                kv.setValue(value);
                            }else{
                                value.add(kv.getValue().get(0)); // 否则默认取第一个
                                kv.setValue(value);
                            }
                        }
                    });
        }));
    }
}
4) 使用代理

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

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

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

6)Node.js中间件

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

注意事项
  • 在开发环境中解决跨域问题时,要注意不要将敏感信息(如Token)暴露给不安全的跨域请求。
  • 在生产环境中,推荐使用Nginx反向代理或其他服务器端解决方案,以提高安全性。
  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值