协议(protocol),主机(host)和端口号
前端
服务器的协议,IP和端口和后端
服务器不一样
,就产生了跨域CORS
是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)克服了AJAX只能同源使用的限制
只要同时满足以下两大条件,就属于简单请求
GET
POST
Content-Type
:只限制三个值
Origin
字段协议 + 域名 + 端口
)。服务器根据这个值,决定是否同意这次请求举例
GET
,响应response添加自定义头信息X-Name
不在许可范围内
,服务器会返回一个正常的HTTP回应
在许可范围内
,服务器返回的响应,会多出几个头信息字段
Access-Control-
开头简单请求响应跨域设置
前端
后端
application/json
"预检"请求
举例
POST
,携带json请求体并添加自定义头信息X-Data
OPTIONS
,表示这个请求是用来询问的。头信息里面,关键字段是Origin
,表示请求来自哪个源HTTP方法
头信息字段
Origin
、Access-Control-Request-Method
和Access-Control-Request-Headers
字段以后,确认允许跨源请求,就可以做出回应有效期
,单位为秒Origin
头信息字段Access-Control-Allow-Origin
头信息字段"预检"请求之后,浏览器的正常CORS请求
服务器正常的回应
非简单请求响应跨域设置
前端
后端
@Component
public class CrosFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpServletRequest request =(HttpServletRequest) servletRequest;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "*");
/**
* 如果设置Access-Control-Allow-Credentials,允许跨域请求携带cookie
* 那么Access-Control-Allow-Origin不能为*,只能为指定的域名
*/
// response.setHeader("Access-Control-Allow-Credentials", "true");
// 非预检请求,放行即可,预检请求,则到此结束,不需要放行
if (!"OPTIONS".equalsIgnoreCase(request.getMethod())) {
filterChain.doFilter(servletRequest, servletResponse);
}
}
}
@CrossOrigin源码
RequestMapping修饰的方法
和Controller类上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {
@AliasFor("origins")
String[] value() default {};
@AliasFor("value")
String[] origins() default {};
/**
* @since 5.3
*/
String[] originPatterns() default {};
String[] allowedHeaders() default {};
String[] exposedHeaders() default {};
RequestMethod[] methods() default {};
String allowCredentials() default "";
long maxAge() default -1;
}
@CrossOrigin属性介绍
默认配置是“*”
默认为“*”
表示允许所有的请求头开启该选项时origin不允许设置为“*”
1800s
,在缓存时间内同一请求不需要“预检”请求@CrossOrigin注解比较适用于较细粒度的跨域控制
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
//是否发送Cookie
// .allowCredentials(true)
//放行哪些原始域
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("*")
.exposedHeaders("*");
}
}
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsFilter corsFilter() {
// 1. 添加 CORS配置信息
CorsConfiguration config = new CorsConfiguration();
// 放行哪些原始域
config.addAllowedOrigin("*");
// 是否发送 Cookie
// config.setAllowCredentials(true);
// 放行哪些请求方式
config.addAllowedMethod("*");
// 放行哪些原始请求头部信息
config.addAllowedHeader("*");
// 暴露哪些头部信息
config.addExposedHeader("*");
// 2. 添加映射路径
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**", config);
// 3. 返回新的CorsFilter
return new CorsFilter(corsConfigurationSource);
}
}
CorsFilter过滤器
DefaultCorsProcessor
DefaultCorsProcessor类的handleInternal方法
protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,
CorsConfiguration config, boolean preFlightRequest) throws IOException {
String requestOrigin = request.getHeaders().getOrigin();
// 从配置类CorsConfiguration获取允许的域名
String allowOrigin = checkOrigin(config, requestOrigin);
HttpHeaders responseHeaders = response.getHeaders();
if (allowOrigin == null) {
logger.debug("Reject: '" + requestOrigin + "' origin is not allowed");
rejectRequest(response);
return false;
}
// 获取请求的请求方法
HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);
// 从配置类CorsConfiguration获取允许的请求方法
List<HttpMethod> allowMethods = checkMethods(config, requestMethod);
if (allowMethods == null) {
logger.debug("Reject: HTTP '" + requestMethod + "' is not allowed");
rejectRequest(response);
return false;
}
// 获取请求头
List<String> requestHeaders = getHeadersToUse(request, preFlightRequest);
// 从配置类CorsConfiguration获取允许的请求头
List<String> allowHeaders = checkHeaders(config, requestHeaders);
if (preFlightRequest && allowHeaders == null) {
logger.debug("Reject: headers '" + requestHeaders + "' are not allowed");
rejectRequest(response);
return false;
}
// 设置响应允许域名
responseHeaders.setAccessControlAllowOrigin(allowOrigin);
// 如果是预检请求,设置允许请求方法
if (preFlightRequest) {
responseHeaders.setAccessControlAllowMethods(allowMethods);
}
// 设置允许请求头
if (preFlightRequest && !allowHeaders.isEmpty()) {
responseHeaders.setAccessControlAllowHeaders(allowHeaders);
}
// 设置允许响应头
if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
}
// 设置是否允许请求携带cookie
if (Boolean.TRUE.equals(config.getAllowCredentials())) {
responseHeaders.setAccessControlAllowCredentials(true);
}
// 设置预检到期时间
if (preFlightRequest && config.getMaxAge() != null) {
responseHeaders.setAccessControlMaxAge(config.getMaxAge());
}
response.flush();
return true;
}
CorsRegistry原理
@Configuration
public class CorsConfigure {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.addAllowedMethod("*");
config.addAllowedHeader("*");
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource(new PathPatternParser());
configSource.registerCorsConfiguration("/**", config);
return new CorsWebFilter(configSource);
}
}