暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Spring Boot前后端分离项目解决跨域问题的3种方案

Java程序员ZZM 2021-06-21
771

首先了解一下什么是跨域请求(CORS)?

  • 只要客户端和服务端的 请求协议+域名+端口 有一个不相同就是跨域请求。不符合Same Origin Policy
    ,译为“同源策略”。

  • CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。

  • 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

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

  • 整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与普通的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求(预检请求
    ),但用户不会有感知。因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨域通信。

预检请求

  • 非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。

  • 非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。

  • 浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

  • 下面是一段浏览器的JavaScript脚本。

var url = 'http://localhost:8086/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();

上面代码中,HTTP请求的方法是PUT,并且发送一个自定义头信息X-Custom-Header。

  • 浏览器发现,这是一个非简单请求,就自动发出一个"预检"请求,要求服务器确认可以这样请求。下面是这个"预检"请求的HTTP头信息。

OPTIONS /cors HTTP/1.1
Origin: http://localhost:8086
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: localhost:8086
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36

  • "预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。

  • 除了Origin字段,"预检"请求的头信息包括两个特殊字段。

  • Access-Control-Request-Method

  • 该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。

  • Access-Control-Request-Headers

  • 该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。

两种请求

  • 浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

只要同时满足以下两大条件,就属于简单请求。

(1) 请求方法是以下三种方法之一:
HEAD 
GET 
POST 
(2)HTTP头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
这是为了兼容表单(form),因为历史上表单一直可以发出跨域请求。AJAX 的跨域设计就是,只要表单可以发,AJAX 就可以直接发。

凡是不同时满足上面两个条件,就属于非简单请求。浏览器对这两种请求的处理,是不一样的。

跨域的三种解决办法

方法一:全局配置(推荐)

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class EasyWebMvcConfigurer implements WebMvcConfigurer {
    /**
     * 解决静态资源无法访问
     * @param registry ResourceHandlerRegistry
     * @author zmzhou
     * @date 2020/07/02 19:35
     */

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 解决静态资源无法访问
        registry.addResourceHandler("/**")
                .addResourceLocations("classpath:/static/")
                .addResourceLocations("classpath:/templates/");
    }
    /**
     * 允许所有跨站请求
     * @param registry CorsRegistry
     * @author zmzhou
     * @date 2020/07/03 15:09
     */

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // Add more mappings... 可以添加多个mapping
        registry.addMapping("/**")
            // 服务器支持的所有头信息字段
            .allowedHeaders("*")
            // 服务器支持的所有跨域请求的方法
            .allowedMethods("POST""GET""PUT""DELETE""OPTIONS""HEAD")
            // 是否允许发送Cookie
            .allowCredentials(true)
            // 指定本次请求的有效期
            .maxAge(1800)
            // 设置允许跨域请求的域名
            .allowedOriginPatterns("*");
    }
}

方法二:基于过滤器 这种方式很容易理解,就是在每个请求的response中写入 Access-Control-* 这些响应头

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Configuration;
@WebFilter(filterName = "myCorsFilter")
@Configuration
public class MyCorsFilter implements Filter {
  @Override
  public void doFilter(ServletRequest request, ServletResponse res, FilterChain chain) throws IOException,
    ServletException 
{
    HttpServletResponse response = (HttpServletResponse) res;
    // 设置允许跨域请求的域名
    response.setHeader("Access-Control-Allow-Origin""http://localhost:8086/");
    // 是否允许发送Cookie
    response.setHeader("Access-Control-Allow-Credentials""true");
    // 服务器支持的所有跨域请求的方法
    response.setHeader("Access-Control-Allow-Methods""POST,GET,PUT,DELETE,OPTIONS,HEAD");
    // 指定本次请求的有效期
    response.setHeader("Access-Control-Max-Age""1800");
    // 服务器支持的所有头信息字段
    response.setHeader("Access-Control-Allow-Headers""Origin, X-Requested-With," +
      " Content-Type, Content-Language, Accept, Accept-Language, Authorization");
    chain.doFilter(request, response);
  }
}

基于过滤器第二种写法,使用org.springframework.web.filter.CorsFilter

import java.util.Arrays;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.context.annotation.Configuration;
@Configuration
public class EasyWebMvcConfigurer {
  @Bean
  public CorsFilter corsFilter() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOrigin("http://localhost:8086");
    config.setAllowedHeaders(Arrays.asList(("Origin,X-Requested-With,Content-Type,Content-Language,Accept," +
      "Accept-Language,Authorization").split(",")));
    config.setAllowedMethods(Arrays.asList("POST,GET,PUT,DELETE,OPTIONS,HEAD".split(",")));
    config.setMaxAge(1800L);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);
    return new CorsFilter(source);
  }
}

方法三:@CrossOrigin 注解,可以放在 method 或者 class 上

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/login")
@CrossOrigin(origins = "http://localhost:8086")
public class LoginController {
}


怎么让你的U盘不再中毒?

手把手教你搭建一个炫酷的GitHub-pages开源项目静态网站

Keepalived + Nginx 实现高可用 Web 负载均衡

tomcat+redis+Redisson实现session共享

文章转载自Java程序员ZZM,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论