一、问题背景:
Same Origin Policy(SOP同源策略):具有相同的Origin即拥有相同的协议、主机地址及端口。目的是防止某个文档或者脚本从多个不同源的地址装载(其他站点转载内容不安全)。
二、CORS简介
跨域资源共享(CORS:Cross-origin resource sharing):它允许浏览器向跨源服务器发出XMLHTTP Request请求,从而克服了AJAX只能同源使用的限制。CORS需要浏览器和服务器同时支持,它的通信由浏览器自动完成,不需要用户参与。CORS通信的关键在于服务器,服务器需要实现CORS接口。
三、CORS请求分两类
浏览器发出的CORS简单请求,只需要在头信息中增加一个Origin字段。
- 简单请求(simple request):HEAD、GET、POST请求,并且HTTP的头信息仅为以下几种字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type(仅限3个值:application/x-www-form-urlencoded、multipart/form-data、text/plain)
- 非简单请求(not-so-simple):不在简单请求范围的为非简单请求。浏览器发出非简单请求会在正式通信之前增加一次OPTIONS查询请求,称为“预检”请求(preflight)。浏览器先访问服务器,当前网页所在域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会正式发出XMLHttpRequest请求,否则报错。
四、详解响应头
- Access-Control-Allow-Origin(必填):请求时Origin字段的具体值或者*(表示接受任意域名的请求)
- Access-Control-Allow-Methods(必填):逗号分割的一个具体字符串或者*,表示服务器支持的所有跨域请求方法。返回的是所有支持的方法而不单是浏览器请求的那个方法,避免多次“预检”请求。
- Access-Control-Expose-Headers(可选):CORS请求XMLRequest对象的getResponseHeader方法只能拿到6个基本字段(Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma)如果需要拿到其他字段,就必须在这里指定。
- Access-Control-Allow-Credentials(可选):布尔值(表示是否允许发送Cookie,默认false),对服务器有特殊要求的请求(PUT、DELETE、Content-Type的类型为application/json)时这个值只能为true,如果服务器不需要浏览器发送Cookie,删除即可。
- Access-Control-Max-Age(可选):指定本次预检请求的有效期,单位为秒,有效期内不用再发预检请求。如果发现开发中每次请求都是2条(一次是OPTIONS,一次是正常请求)那么就需要配置此字段,避免每次都发出预检请求。
五、解决方案
5.1、全局配置,实现WebMvcConfigurer接口的addCorsMappings方法
package com.prison.intranet.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @ClassName CorsConfig
* @Description
* @Author WangJing
* @Date 2021/7/29 10:11 上午
* @Version V1.1.0
*/
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
//是否发送Cookie
.allowCredentials(true)
//放行哪些原始域
.allowedOrigins("*")
.allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"})
.allowedHeaders("*")
.exposedHeaders("*");
}
}
5.2、全局Filter配置,实现javax.servlet.Filter接口的doFilter方法(亲测,推荐)
package com.prison.extranet.config;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @ClassName CorsFilter
* @Description
* @Author WangJing
* @Date 2021/7/29 10:13 上午
* @Version V1.1.0
*/
@Component
@WebFilter(urlPatterns = "/*", filterName = "authFilter") //这里的“/*” 表示的是需要拦截的请求路径
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PATCH, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
chain.doFilter(req, res);
}
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void destroy() {
}
}
5.3、@CrossOrigin注解,类型@RequestMapping注解的使用(最小粒度的控制,精确到单个请求级别)
@CrossOrigin(origins = "http://localhost:8001", maxAge = 3600)
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.web.bind.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {
/** @deprecated */
@Deprecated
String[] DEFAULT_ORIGINS = new String[]{"*"};
/** @deprecated */
@Deprecated
String[] DEFAULT_ALLOWED_HEADERS = new String[]{"*"};
/** @deprecated */
@Deprecated
boolean DEFAULT_ALLOW_CREDENTIALS = false;
/** @deprecated */
@Deprecated
long DEFAULT_MAX_AGE = 1800L;
@AliasFor("origins")
String[] value() default {};
@AliasFor("value")
String[] origins() default {};
String[] originPatterns() default {};
String[] allowedHeaders() default {};
String[] exposedHeaders() default {};
RequestMethod[] methods() default {};
String allowCredentials() default "";
long maxAge() default -1L;
}
从元注解@Target可以看出,注解可以放在method、class等上面,类似RequestMapping,也就是说,整个controller下面的方法可以都受控制,也可以单个方法受控制,这个是最小粒度的cors控制办法了,精确到单个请求级别。
其中@CrossOrigin中的2个参数:
- origins: 允许可访问的域列表
- maxAge:准备响应前的缓存持续的最大时间(以秒为单位)。
5.4、总结:一般使用前两种全局配置即可,同时配置就近原则生效
注:以上内容仅提供参考和交流,请勿用于商业用途,如有侵权联系本人删除!?
|