参考 B站 从原理上解答跨域解决方案,不要错过哦,真的挺简单【有代码实战】

参考 学成在线

解决跨域问题

跨域限制是浏览器行为,不是服务器行为。但是服务器可以辅助允许跨域请求。

浏览器有个同源策略,对于不同源的站点之间的相互请求会做限制。如果非同源,共有三种行为受到限制:

  • Cookie、LocalStorage 和 IndexDB 无法读取。
  • DOM 无法获得。
  • AJAX 请求不能发送。(最常见)
    • 跨域限制仅仅是浏览器的行为,通过代理服务器,或者其他工具发送请求就能轻松绕过

问题出现时机: 前后端联调

问题描述:

如果发生了跨域访问(或者说,一定要跨域),需要

  • 在跨域的请求请求头添加Origin字段,给服务器说明,这是一个跨域请求
  • 服务端根据设定的规则,判断是否允许跨域如果允许,则会在响应头中添加 Access-Control-Allow-Origin字段,如
    • Access-Control-Allow-Origin:http://localhost:8601
    • Access-Control-Allow-Origin:* (允许任何网址的跨域资源共享)

如果客户端发现响应头没有上述允许的标识,就会报错,即使接收到了完整信息,浏览器也会报错

协议执行过程:

浏览器

  • 浏览器先根据同源策略对前端页面和后台交互地址做匹配,若同源,则直接发送数据请求;若不同源,则发送跨域请求

浏览器判断是跨域请求会在请求头上添加origin,表示这个请求来源哪里 (来自于前端当前页面,一般请求),例如

GET / HTTP/1.1
Origin: http://localhost:8601

Origin 指示了请求来自于哪个站点,只有服务器名,不包含路径信息,浏览器自动添加到http请求 Header 中,无需手动设置。

添加 Origin 的情况
同源请求:POST、OPTIONS、PUT、PATCH 和 DELETE请求都会添加Origin请求头,GET或HEAD请求不会添加Origin请求头。
跨域请求:所有跨域请求(CORS)都会添加Origin请求头。

服务器

  • 服务器收到请求会给与响应,响应的header里写明跨域的配置信息,告诉浏览器,它允许哪些域名发来的请求访问,哪些method可以执行。浏览器收到响应后自动判断能不能真正执行请求。

服务器解析程序收到浏览器跨域请求后,根据自身配置返回对应文件头。若未配置过任何允许跨域,则文件头里不包含Access-Control-Allow-origin字段,若配置过域名,则返回Access-Control-Allow-origin+ 对应配置规则里的域名的方式。

  • 如果允许则在响应头中说明允许该来源的跨域请求,如下
Access-Control-Allow-Origin:http://localhost:8601
  • 如果允许所有域名来源的跨域请求,则响应如下
Access-Control-Allow-Origin:*

一个支持CORS的web服务器,有如下的判定字段,他们会在响应的header中写明

  • Access-Control-Allow-Origin:允许跨域的Origin列表 // 刚刚说的那个
  • Access-Control-Allow-Methods:允许跨域的方法列表
  • Access-Control-Allow-Headers:允许跨域的Header列表
  • Access-Control-Expose-Headers:允许暴露给JavaScript代码的Header列表Access-Control-Max-Age:最大的浏览器缓存时间,单位为s

其中Access-Control-Allow-Origin(访问控制之允许的源),在响应的http header中必须有的,表示允许访问本服务器的源头Origin(域名),可以是特定的域名列表,用逗号分隔,也可以是通配符 *,表示支持任意域名的访问。
除了限定源头Origin,还会限制请求的方法Method,Header。

如,如果服务器设定Access-Control-Allow-Methods:GET,那么跨域的POST请求无法在这个服务器执行。

再到浏览器:

浏览器根据接受到的http文件头里的Access-Control-Allow-origin字段做匹配,若无该字段,说明不允许跨域;若有该字段,则对字段内容和当前域名做比对,如果同源,则说明可以跨域,浏览器发送该请求;若不同源,则说明该域名不可跨域,不发送请求。

(但是不能仅仅根据服务器返回的文件头里是否包含Access-Control-Allow-origin来判断其是否允许跨域,因为服务器端配置多域名跨域的时候,也会出现不能跨域的域名返回包里没有Access-Control-Allow-origin字段的情况。)

总结

1.页面发送请求
2.浏览器根据同源策略做出判定,如果是同源请求,直接发送出去;如果是跨域请求,在HTTP HEADER加上Origin字段,或是先发送一次预检请求(preflight)。
3.服务器接收请求,根据自身跨域的配置(如允许哪些域名,什么样的Method访问),返回文件头。若未配置过任何允许跨域,则文件头里不包含Access-Control-Allow-origin字段,若配置过域名,则返回Access-Control-Allow-origin+ 对应配置规则里的域名的方式。

浏览器接收到响应,根据响应头里的Access-Control-Allow-origin字段做匹配,如果没有这个字段,说明不匹配;如果有,将字段内容和当前域名做比对。如匹配,则可以发送请求。

解决跨域的方法

前端 (浏览器) JSONP

原理: 相当于规避,跨域策略资源不限制图片标签 script标签等,可以将跨域请求的资源 放在 script标签的 src属性当中

  • 通过script标签 的src属性进行跨域请求,如果服务端要响应内容,则先读取请求参数callback值,callback是一个回调函数的名称,服务端读取callback的值后将响应内容通过调用callback函数的方式告诉请求方

img

服务端:添加响应头

  • 服务端在响应头添加Access-Control-Allow-Origin: *

在 Spring Boot 中为特定响应添加标头

参考以上博文,我将system查询代码做了如下修改,果然成功了

@GetMapping("/dictionary/all")
public List<Dictionary> queryAll(HttpServletResponse httpServletResponse) {
    httpServletResponse.setHeader("Access-Control-Allow-Origin","*");
    return dictionaryService.queryAll();
}

服务端:通过nginx代理跨域

如下图:当前访问的页面是http://192.168.101.10:8601 但是它里面异步访问了http://www.baidu.com:8601

如此会被判定为跨域,访问失败(红色箭头)

img

举例: 添加响应头

在被跨域访问资源的服务器 (xuecheng-plus-system-api)模块下新建配置类GlobalCorsConfig

@Configuration
public class GlobalCorsConfig {

    @Bean
    public CorsFilter getCorsFilter() {
        CorsConfiguration configuration = new CorsConfiguration();
        //添加哪些http方法可以跨域,比如:GET,Post,(多个方法中间以逗号分隔),*号表示所有
        configuration.addAllowedMethod("*");
        //添加允许哪个请求进行跨域,*表示所有,可以具体指定http://localhost:8601 表示只允许http://localhost:8601/跨域
        configuration.addAllowedOrigin("*");
        //所有头信息全部放行
        configuration.addAllowedHeader("*");
        //允许跨域发送cookie
        configuration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", configuration);
        return new CorsFilter(urlBasedCorsConfigurationSource);
    }
}
  • 此配置类实现了跨域过滤器,在响应头添加Access-Control-Allow-Origin

  • 重启系统管理服务,前端工程可以正常进入http://localhost:8601 ,查看NetWork选项卡,跨域问题成功解决

img