使用CORS解决跨域问题

1 跨域问题是什么

       先说跨域,跨域是指跨域名(通信协议+域名+端口)的访问;而跨域不一定会产生跨域问题,跨域问题的产生是浏览器对于ajax请求的一种安全限制,一个页面发起的请求必须是与当前域名一样,否则,会产生跨域问题。

1.1 发生跨域问题时,浏览器的console会报出什么错

Access to XMLHttpRequest at 'http://localhost:8089/api/user/list?key=&page=1&rows=5' from origin 'http://sea.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

        以上信息是在chrome的开发者工具中获取的,显示的就是一个跨域问题的错误,错误产生的原因是前端项目部署的域名为sea.com,而在前端的项目中又去调用了部署域名为localhost的后台接口,因此产生了跨域问题。

1.2 什么情况下算跨域

        当在浏览器中访问前端项目,称这个前端项目为项目A,在项目A中会调用另一个后台项目的接口,这里称这一个后台项目为项目B。前端项目A和后端项目B之间总结为有四种跨域的情况。

(1)协议不同的情。比如https://www.blog.csdn.net与http://www.bog.csdn.net;

(2)域名不同的情况。比如www.baidu.com与www.qq.com;

(3)端口不同的情况。比如www.csdn.com:8089与www.csdn.com:8080;

(4)二级域名不同的情况。blog.csdn.net与mp.csdn.net.

2 跨域问题的解决方案

2.1 jsonp

        jsonp是我所听说的最早的跨域问题解决方案,但是存在着缺点。它只能发送GET请求,即使是在ajax中配置的是POST请求,如果是使用的jsonp,也会将POST请求转为GET请求;另外,使用jsonp也需要后台服务的支持。

2.2 使用nginx

        之前在一个项目前后端联调时有用过nginx反向代理解决跨域问题,其思想就是让nginx去代理后端的接口,但是nginx暴露的是前端的ip及端口,这样让前端的js看起来访问的是自己的ip与端口,但实际上由于nginx做了代理,其最后真正访问的是另一个地址。而使用nginx需要进行额外的配置,也不是本文所描述的重点。

3.3 CORS

        CORS(Cross-origin resource sharing)是一个W3C标准,全称为“跨域资源共享”,是目前最为流行的跨域解决方案,现在一般的互联网公司会在网关中配置一个CORS的拦截器,并配合nginx的反向代理实现跨域问题的解决,当然在生产环境使用nginx不止是解决跨域问题的一部分。

3.3.1 原理

        在前端这边,使用ajax进行CORS通信与原有的写法无任何区别,浏览器会自己处理(远古浏览器不支持),处理的方式根据请求类型分为两种,一种是在“普通请求”的情况下,“普通请求”需要满足以下条件:

(1)请求的方式为HEAD、GET、POST中的一种;

(2)Request Headers中最多只包含Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type五个字段,并且Content-Type只能是`application/x-www-form-urlencoded`、`multipart/form-data`、`text/plain`这三个值当中的。

       当请求为“普通请求”时,会在Request Headers中携带一个Origin的参数,这一个参数的值为当前请求所属的域(通信协议+ 域名+端口),服务器端根据这个值判断是否允许访问(是否允许访问的值由服务器端的开发人员编写)。如果服务器端允许该域的访问,会在Response Headers携带以下信息,包括:

Access-Control-Allow-Origin: http://localhost:8080
Access-Control-Allow-Credentials: true

其中,Access-Control-Allow-Origin的值表示允许跨域访问的地址,Access-Control-Allow-Credentials为true时表示允许携带cookie。

        那么,当请求不满足“普通请求”时,浏览器则认为这是一个“特殊请求”,“特殊请求”会在浏览器正式发起请求前,对服务器发起一次“预检”的请求,也就是向服务器询问我目前的域是否在可访问服务器的域列表中,如果不能,就会报错。当然,如果每一次发起“特殊请求”都会去“询问”服务器是否可以访问肯定是浪费资源,而且显得很傻,那么在服务端可以配置一个这样的值,这一个值设定了当允许这一个域的访问后,在一个规定的时间范围内,都是可以不经过“预检”而进行访问的。

3.3.2 实现

        在SpringMVC中有一个org.springframework.web.cors.CorsConfiguration的类,我们只要围绕它去编写代码,并将编写好的代码注册到Spring中就可以了,在实际的环境中,我们一般把这一个bean放到网关的服务中去,比如spring cloud的zuul网关服务中。以下为模板代码:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        // 1.CORS配置信息
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("http://localhost:8080");   // 允许的域,如果值为*,会导致无法使用cookie
        config.setAllowCredentials(true);   // 是否可以使用cookie
        config.addAllowedMethod("OPTIONS"); // 允许的请求类型
        config.addAllowedMethod("HEAD");
        config.addAllowedMethod("GET");
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("DELETE");
        config.addAllowedMethod("PATCH");
        config.addAllowedHeader("*");   // 允许的头信息
        config.setMaxAge(1800L);        // 有效时长,也就是当请求为“特殊请求”时,一次“询问”是否可以访问后,可以保持1800秒不需要进行校验
        // 2. 映射路径,写/**表示拦截所有请求
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config);
        // 3.返回
        return new CorsFilter(configSource);
    }
}
发布了48 篇原创文章 · 获赞 52 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/y506798278/article/details/103772667