使用CORS解决跨域问题

1 跨域问题

1.1 什么叫跨域问题

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

1.2 发生跨域问题时,浏览器的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.3 总结四种跨域场景

        假设现在有一个前后端分开部署的系统,前端项目部署称为服务A,后端项目部署称为服务B,前端会调用后端提供的接口,也就是服务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去代理后端的服务,并提供与前端域名相同的访问入口,这样让前端的JS“看起来”访问的是自己的ip与端口,但实际上由于nginx做了代理,其最后真正访问的是另一个地址。nginx也是目前非常流行的反向代理与负载均衡的服务器,许多互联网项目中会用到它,当然解决跨域问题只是其功能的一部分。

2.3 CORS

        CORS(Cross-origin resource sharing)是一个W3C标准,全称为“跨域资源共享”,是目前最为流行的跨域解决方案,配置简单,使用方便,下面详细介绍一下这个CORS。

 

3 CORS的原理及实现

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);
    }
}

 

你可能感兴趣的:(Web开发,跨域,CORS,HTTP,跨域问题)