CORS-跨域资源共享

CORS-跨域资源共享

什么是CORS ?

在前后端分离的项目中,我们往往会遇到跨域问题。

跨域问题只会出现在浏览器发起AJAX(XMLHttpRequest、Fetch)请求的时候,因为浏览器限制了AJAX只能从同一个源请求资源,除非配置了正确的CORS。

CORS被翻译为跨域资源共享(或者跨源资源共享),是一种基于HTTP头的机制,该机制允许服务器标识除自己以外的,使得浏览器允许这些源访问自己的资源。

源就是指请求URL中的协议、域名、端口号

image-20240725143635339

当请求发起者与接收者的协议、域名、端口号三者有任一个不同时即为跨域(跨源),就发生了CORS。

请求发起者请求接受者是否跨域原因
http://www.test.com/http://www.test.com/index.html同源
http://www.test.com/https://www.test.com/协议不同(http、https)
http://www.test.com:8080/http://www.test.com:8088/端口不同(8080、8088)
http://www.test.com/http://www.tesT.com/同源
http://www.a.com/http://www.b.com/域名不同(a、b)

CORS 又分为简单请求和非简单请求

简单请求

在HTML中一般可以通过元素发起简单请求。

简单请求需满足以下条件:

  • 使用以下请求方法之一:
    • HEAD
    • POST
    • GET
  • 除自动配置的标头字段外,仅可人为配置以下字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type
    • Range
  • Content-Type设置的媒体类型仅限以下类型:
    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded
  • 如果请求是使用XMLHttpRequest对象发出的,在返回的XMLHttpRequest.upload对象属性上没有注册任何事件监听器
  • 请求中没有使用ReadableStream对象

非简单请求

非简单请求需要在发送实际请求之前使用OPTIONS方法发送一个预检请求,以获知服务器是否允许该实际请求,避免实际的跨域请求对用户数据产生影响。

Nginx解决跨域问题

使用Nginx反向代理解决跨域问题是较为简单的解决办法,只需要配置nginx.conf配置文件即可:

server {
    #nginx监听所有localhost:8080端口收到的请求
	listen       8080;
	server_name  localhost;
 
	# Load configuration files for the default server block.
	include /etc/nginx/default.d/*.conf;
	
    #localhost:8080 会被转发到这里
	#同时, 后端程序会接收到 "192.168.25.20:8088"这样的请求url
	location / {
		proxy_pass http://192.168.25.20:8088;
	}
	
	#localhost:8080/api/ 会被转发到这里
    #同时, 后端程序会接收到 "192.168.25.20:9000/api/"这样的请求url
	location /api/ {
		proxy_pass http://192.168.25.20:9000;
	}
	
	error_page 404 /404.html;
		location = /40x.html {
	}
	
	error_page 500 502 503 504 /50x.html;
		location = /50x.html {
	}
}

Spring 解决跨域问题

WebMvcConfigurer

可以通过重写WebMvcConfigurer中的***addCorsMappings(CorsRegistry registry)***方法来配置全局跨域设置:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer{
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")                  // 允许跨域请求的path,支持路径通配符,如:/api/**
            .allowedOrigins("*")                    // 允许发起请求的源
            .allowedHeaders("*")                    // 允许客户端的提交的 Header,通配符 * 可能有浏览器兼容问题
            .allowedMethods("GET")                  // 允许客户端使用的请求方法
            .allowCredentials(false)                // 不允许携带凭证
            .exposedHeaders("X-Auth-Token, X-Foo")  // 允许额外访问的 Response Header
            .maxAge(3600)                           // 预检缓存一个小时
            ;
    }
}

@CrossOrigin

为了更精确的配置跨域,可以在Controller类或其方法上添加注解***@CrossOrigin***

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/foo")
@RestController
@CrossOrigin(
    	origins = "*",             // 允许的源,也就是 allowedOrigins
        allowCredentials = "false",     // 不允许提交cookie
        allowedHeaders = "*",           // 允许的请求头
        exposedHeaders = "*",           // 允许客户端额外读取的响应头
        maxAge = 3600,                  // 缓存时间
        methods = {RequestMethod.GET, RequestMethod.HEAD} // 允许客户端的请求方法
)
public class FooController {
    
}

CorsFilter

不管是WebMvcConfigurer还是**@CrossOrigin**都是硬编码的方式,不够灵活。

Spring提供了一个CorsFilter让我们以编程式的方式更灵活的配置跨域。

import java.time.Duration;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.util.StringUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer{

    // 通过 FilterRegistrationBean 注册 CorsFilter
    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        
        // 跨域 Filter
        CorsFilter corsFilter = new CorsFilter(request -> {
            
            // 请求源
            String origin = request.getHeader(HttpHeaders.ORIGIN);
            
            if (!StringUtils.hasText(origin)) {
                return null; // 非跨域请求
            }
            
            // 针对每个请求,编程式设置跨域
            CorsConfiguration config = new CorsConfiguration();
            
            // 允许发起跨域请求的源,直接取 Origin header 值,不论源是哪儿,服务器都接受
            config.addAllowedOrigin(origin);
            
            
            // 允许客户端的请求的所有 Header
            String headers = request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
            if (StringUtils.hasText(headers)) {
                config.setAllowedHeaders(Stream.of(headers.split(",")).map(String::trim).distinct().toList());
            }
            
            // 允许客户端的所有请求方法
            config.addAllowedMethod(request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD));
            // 允许读取所有 Header
            // 注意,"*" 通配符,可能在其他低版本浏览中不兼容。
            config.addExposedHeader("*");
            // 缓存30分钟
            config.setMaxAge(Duration.ofMinutes(30));
            // 允许携带凭证
            config.setAllowCredentials(true);
            return config;
        });
        
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(corsFilter);
        bean.addUrlPatterns("/*");                  // Filter 拦截路径
        bean.setOrder(Ordered.LOWEST_PRECEDENCE);   // 保证最先执行
        return bean;
    }
}

不管是什么方式,当allowCredentials为true允许携带凭证时,allowedOrigins不能再使用通配符“*”,必须填写确切的源。

凭证通常是包含用户标识的Cookie

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值