深入理解解决跨域问题vue+Springboot

1. 背景

  • 前端地址:http://localhost:9500
  • 后端地址:http://localhost:11000
  • 问题一:访问简单请求出错
    在这里插入图片描述
  • 问题二:访问非简单请求出错
    在这里插入图片描述

浏览器将CORS请求分为两类:简单请求和非简单请求。
只要满足以下两个条件,就是简单请求,否则就是非简单请求。

  1. 请求方法是以下三种方法之一:
  • HEAD
  • GET
  • POST
  1. HTTP的头信息不超出以下几种字段:
  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

2. 解决过程

2.1. 解决方案一
2.11. 解析错误信息

Access to XMLHttpRequest at 'http://localhost:11000/api/plugin/list' from origin 'http://localhost:9500' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

翻译:来自’http://localhost:9500’的 'http://localhost:11000/api/plugin/list’请求已经被CORS policy拦截了。在被请求的资源上没有“Access-Control-Allow-Origin”头信息。

意思是:请求的资源已经返回了,但是返回的资源里没有“Access-Control-Allow-Origin”头信息,所以被浏览器的CORS策略拦截了。查看服务端的日志信息可以发现,请求确实成功返回了。
服务端信息

  • 首先,这个错误是由于浏览器的同源策略导致的跨域问题。同源策略要求协议(http)、端口(9500)、域名(localhost)都相同。其中一个不同,都被当做是不同的域。9500和11000端口不同,所以被CORS策略blocked。

  • CORS:Cross-origin resource sharing,跨域资源共享。允许浏览器访问跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

2.12 问题一出现的过程分析
  • 问题一属于简单的GET请求,浏览器直接发出CORS请求。具体来说就是头信息中,增加一个Origin字段。
    客户端
    上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。

error

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

  • Access-Control-Allow-Origin: http://api.bob.com
  • Access-Control-Allow-Credentials: true
  • Access-Control-Expose-Headers: FooBar
  • Content-Type: text/html; charset=utf-8

上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头。
(1)Access-Control-Allow-Origin
该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
(2)Access-Control-Allow-Credentials
该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
(3)Access-Control-Expose-Headers
该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader(‘FooBar’)可以返回FooBar字段的值。

2.13 解决方案之服务端设置允许跨域
  • 方案1. controller对应的方法里添加“Access-Control-Allow-Origin”
    @GetMapping("/list")
    public List<PluginResponse> getUncheckPlugins(HttpServletResponse response) {
        response.setHeader("Access-Control-Allow-Origin", "*");
        return pluginService.getcheckedPlugins(); }

但是,这种方法需要在服务端每一个接口里面设置,传参还需多加一个HttpServletResponse response,超级不优雅,不推荐!!!

  • 方案2. 使用spring框架提供的注解@CrossOrigin
    注解
  • 方案3. 使用WebMvcConfigurer接口、拦截器、过滤器
  1. Springboot 通过实现WebMvcConfigurer接口来定制Spring MVC配置,例如拦截器。WebMvcConfigurer接口提供了 default void addCorsMappings(CorsRegistry registry) {}方法,专门用来处理跨域请求。

WebMvcConfigurer学习链接

定义一个类实现WebMvcConfigurer接口,使用注解@Configuration注入Springboot loC容器

@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedMethods("GET", "POST", "HEAD", "PUT");
    }
}
  1. 使用拦截器,稍微比上面麻烦了点,实现此方案纯粹为了帮助理解跨域问题。不推荐!!!

拦截器快速学习

使用拦截器第一步:定义拦截器

public class MyHandleInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        boolean res =  HandlerInterceptor.super.preHandle(request, response, handler);
        response.setHeader("Access-Control-Allow-Origin", "*");
        return res;
    }

//    @Override
//    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView){
//        response.setHeader("Access-Control-Allow-Origin", "*");
//        Logger.getLogger("MyHandleInterceptor").info("进入postHandle");
//    }
}

注意,一开始使用postHandle方法实现,但是设置response的header信息不起效,查信息和看源码后,postHandler在response信息生成之后,此时ModelAndView已经返回。要想response生效,需要在preHandle中处理。
源码
第二步:注册拦截器 + 第三步:指定拦截规则

@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyHandleInterceptor()).addPathPatterns("/api/**");
    }
}
  1. 过滤器使用代码不做实现。

过滤器和拦截器参考

2.2. 解决方案二
2.21. 了解nginx

nginx中文文档
nginx1
看了概述还是不知道nginx干嘛的,没关系,我们只需了解nginx可以转发请求即可。

2.22. 解决方案之nginx

使用nginx第一步:下载nginx

下载安装nginx

使用nginx第二步:修改conf目录下的配置文件nginx.conf

原先的配置基本不用修改,关键部分代码如下:

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       8088;
        server_name  localhost;

        location /api {
            proxy_pass http://127.0.0.1:11000/api;
           
            #告诉浏览器允许跨域访问的方法
            add_header Access-Control-Allow-Methods *;
            # 告诉浏览器缓存OPTIONS预检请求1小时
            add_header Access-Control-Max-Age 3600;
            #允许带有cookie访问
            add_header Access-Control-Allow-Credentials true;
            #注意 * 不能满足带有cookie的访问,Origin 必须是全匹配,这里通过变量获取
            add_header Access-Control-Allow-Origin $http_origin;
            #设置支持所有的自定义请求头
            add_header Access-Control-Allow-Headers $http_access_control_request_headers;
            #如果预检请求,则返回成功,不需要转发到后端
            if ($request_method = OPTIONS){
                return 200;
            }
        }
       
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

Nginx监听8088端口,前端需要将目的地址的端口改为8088。add_header指令用于添加返回头字段。可以看到配置中加了跨域相关的配置信息。

3. 总结

  • nginx在项目中使用非常普遍,建议使用nginx直接配置。
  • 网上很多方案是前端需要配置跨域信息,我个人理解是前端无需设置。配置了也没有任何作用。
  • 以上过程都已实证,大家有问题可以交流。

4. 参考资源

阮一峰:跨域资源共享
Springboot教程
拦截器配置
过滤器和拦截器参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值