一、跨域是什么?
跨域(CORS)是指不同域名之间相互访问,顾名思义,指的是浏览器不能执行其他网站的脚本,它是由浏览器的同源策略所造成的,是浏览器对于JavaScript所定义的安全限制策略。
1.1 同源策略
跨域问题其实就是浏览器的同源策略所导致的。
同源策略是一个重要的安全策略,它用于限制一个origin(opens new window)的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。
当跨域时会收到以下错误:
1.2 同源实例
例:https://www.xxx.com:80/path/to/myfile.html?key1=value1#Somewhere
只有当protocol(协议)、domain(域名)、port(端口)三者一致,才是同源。
1.3 简单请求和非简单请求
常见的简单请求:
1、请求方法为:HEAD、GET、POST中的一种;
2、HTTP请求头中字段不超过:Accept、Accept-Language、Content-Language、Last-Event-ID;
3、Content-Type字段值为application/x-www-form-urlencoded、multipart/form-data、text/plain中的一种。
非简单请求:
1、请求方法为put、delete;
2、发送JSON格式的ajax请求;
3、http中带自定义请求头。
对于简单请求:
浏览器发现是跨域请求,就会自动在请求头中加上Origin字段,代表请求来自哪个域(协议+主机名+端口号)。服务器在收到请求后,根据请求头中Origin字段值来判断是否允许跨域请求通过。具体实现方法是:在响应头Access-Control-Allow-Origin字段中设置指定的域名,表示允许这些域名的跨域请求。如果请求头中Origin字段的域名包含在这些域名中,则可以实现跨域请求(当然有时候还需要结合其他字段来判断),否则不通过。例如:
请求头信息 |
GET /cors http 1.1 Origin:http://localhost:8080/ connection:keep-active |
响应头信息 |
Access-Control-Allow-Origin://localhost:8080/ Access-Control-Allow-Credentials:true Content-Type:text/html;chatset=utf-8 |
非简单请求:
非简单请求在发送http请求时,会预先发送一次“预检”(OPTIONS)请求。预检请求会事先询问服务器,当前域名是否在服务器允许的范围内,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复后,浏览器才会真正发出http请求,否则就会报错。
解决跨域可以在客户端访问服务端时拦截以下请求头设置:
Access-Control-Allow-Origin |
标识允许哪个域的请求 |
Access-Control-Allow-Methods |
请求方法 |
Access-Control-Max-Age |
本次预检请求的有效期,单位为秒 |
Access-Control-Allow-Headers |
响应首部 |
Access-Control-Allow-Credentials |
允许客户端携带验证信息 |
2. 如何解决跨域
2.1 JSONP
JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。Jsonp实现的前提:浏览器允许跨越加载同源数据。即在JavaScript脚本中发送请求,就可以远程加载js格式数据。JSONP只支持GET请求。
请求原理:
(1)异步请求的时候,加上一个名为callback的回调函数(该函数前后端要保持一致);
(2)在接口中,将返回的json格式数据,伪装(包装)成js脚本格式;
(3)得到js格式数据后,提取里面的json数据。
Jquery在发送一个Ajax jsonp请求时,会在访问链接的后面自动加上一个验证参数,这个参数是Jquery随机生成的,例如链接:https://www.xxx.com/path/to/myfile?callback=jQuery311098773333_23224424444222,jsonp对接的后端接口:
@ResponseBody @RequestMapping("/getMyJsonp") public String getMyJsonpSuccess(@RequestParam("callback") String callback){ Gson gson=new Gson(); Map map = new HashMap<>(); map.put("seat","测试jsonp接口"); return callback+"("+gson.toJson(map)+")"; }
2.2 拦截请求头
2.2.1使用Filter方式进行设置
使用Filter过滤器来过滤服务请求,向请求端设置Response Header(响应头部)的Access-Control-Allow-Origin属性声明允许跨域访问。
@WebFilter 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-Methods", "*"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "*"); response.setHeader("Access-Control-Allow-Credentials", "true"); chain.doFilter(req, res); } }
2.2.2继承 HandlerInterceptorAdapter(拦截器方式)
@Component public class CrossInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "*"); response.setHeader("Access-Control-Allow-Credentials", "true"); return true; } }
2.2.3实现WebMvcConfigurer
@Configuration @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection") public class AppConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // 拦截所有的请求 .allowedOrigins("http://www.xxx.com") // 可跨域的域名,可以为 * .allowCredentials(true) .allowedMethods("*") // 允许跨域的方法,可以单独配置 .allowedHeaders("*"); // 允许跨域的请求头,可以单独配置 } }
2.3 使用 @CrossOrgin 注解
@CrossOrigin @RestController @RequestMapping("/user") public class UserController { @GetMapping("/{id}") public User get(@PathVariable Long id) { } @DeleteMapping("/{id}") public void remove(@PathVariable Long id) { } }
@CrossOrgin源码如下:
@Target({ElementType.METHOD, ElementType.TYPE}) @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 = true; /** @deprecated */ @Deprecated long DEFAULT_MAX_AGE = 1800L; @AliasFor("origins") String[] value() default {}; @AliasFor("value") String[] origins() default {}; String[] allowedHeaders() default {}; String[] exposedHeaders() default {}; RequestMethod[] methods() default {}; String allowCredentials() default ""; long maxAge() default -1L; }
其本质也是通过拦截请求头进行处理。
2.4 使用Nginx配置
location / { add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Headers X-Requested-With; add_header Access-Control-Allow-Methods GET,POST,PUT,DELETE,OPTIONS; if ($request_method = 'OPTIONS') { return 204; } }