跨域相关知识整理

最近在配置Spring Security的时候在跨域的问题上吃了点亏, 遇到了一些问题, 在这里整理一下, 是跨域相关知识的一篇基础文.

什么是跨域

跨域, 即”跨域资源共享”(Cross-Origin Resource Sharing, 缩写作CORS).
关于CORS, MDN文档中有关CORS的部分给出定义 👇

当一个资源从与该资源本身所在的服务器不同的域或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。
比如,站点http://domain-a.com的某HTML页面通过 <img> 的src请求 http://domain-b.com/image.jpg。网络上的许多页面都会加载来自不同域的CSS样式表,图像和脚本等资源。
出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求。 例如,XMLHttpRequest和Fetch API遵循同源策略。 这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非使用CORS头文件。

为什么PostMan模拟的请求不会涉及跨域

有的时候你会发现, 有些请求你写在前端, 在访问进行请求的时候, 会报跨域的错, 而PostMan模拟同样的请求却不会报错, 能够成功拿到数据, 这又是为什么?

跨域的定义是当一个资源从与该资源本身所在的服务器不同的域或端口请求一个资源时,资源会发起一个跨域 HTTP 请求, 所以你知道了, PostMan之所以不会有跨域的问题是因为它的每一个请求都是单一的, 都是本地直接向后端服务器发出的一个请求, 但是如果你写在前端里, 再到浏览器中去请求, 那么浏览器就会认为你现在是在一个网站向后端服务器(后端的域肯定不会与当前端的域相同吧, 因为就算ip地址一样, 端口也肯定不一样,当然了, 现在说的是前后端分离的情况)请求, 所以这就是跨域.

HTTP的OPTIONS方法

跨域资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。

因此你如果想要在进行相关操作的话, 必须要将OPTIONS请求方法放开, 否则无法进行操作. 如在SpringBoot中使用Security的话, 可以在Security的配置(例如在初试Spring Security一文中的SecurityConfig类的configure方法)中添加放开规则 👇

.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()

SpringBoot项目配置CORS

这里还是以初试Spring Security一文中的项目为例.

我们要实现javax.servlet.Filter接口的CORS过滤器是用来设置跨域相关配置的, 代码类似如下, 这里我叫他CORSFilter:

package cn.edu.upc.eduroamcontrolsystembackend.config;

import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import javax.servlet.*;

@Component
public class CORSFilter implements javax.servlet.Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Authorization");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }
}

请注意response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Authorization");代码中的Authorization, 该header字段为项目中设置的token的字段, 例如刷新token, 则是将token放入header的Authorization字段中提交给后端, 如果不添加该字段的话, 后端会直接将请求过滤掉, 因为前端提交的请求中包含了后端所没有允许的header, 导致前端会报Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.的错误.