CORS-跨域资源共享
什么是CORS ?
在前后端分离的项目中,我们往往会遇到跨域问题。
跨域问题只会出现在浏览器发起AJAX(XMLHttpRequest、Fetch)请求的时候,因为浏览器限制了AJAX只能从同一个源请求资源,除非配置了正确的CORS。
CORS被翻译为跨域资源共享(或者跨源资源共享),是一种基于HTTP头的机制,该机制允许服务器标识除自己以外的源,使得浏览器允许这些源访问自己的资源。
源
源就是指请求URL中的协议、域名、端口号

当请求发起者与接收者的协议、域名、端口号三者有任一个不同时即为跨域(跨源),就发生了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



















