前端

梗概

  • CORS会将请求分成两种类型进行处理
    • 简单请求
    • 复杂请求

简单请求的CORS方式

  • 后端需要给响应头中添加指定字段
    • Access-Control-Allow-Origin: 允许的URL
      • 可以使用通配符
        • Access-Control-Allow-Origin:*

实例

Java

那在java服务端给响应头设置 Access-Control-Allow-Origin 可以这么做: 1、添加一个过滤器

public class CrossDomainFilter implements Filter{
    public void init(FilterConfig filterConfig) throws ServletException {}
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse resp = (HttpServletResponse)servletResponse;
        resp.setHeader("Access-Control-Allow-Origin", "http://localhost:8000");
        filterChain.doFilter(servletRequest,servletResponse);
    }
    public void destroy() {}
}

2、然后在web.xml文件中添加过滤器配置:

<filter>
     <filter-name>crossDomainFilter</filter-name>
     <filter-class>com.example.cors.filter.CrossDomainFilter</filter-class>
</filter>
<filter-mapping>
      <filter-name>crossDomainFilter</filter-name>
      <url-pattern>/*</url-pattern>
</filter-mapping>

复杂请求的CORS方式

  • 对于POST请求设置响应头Content-Type为某些值、自定义请求头等情况,浏览器会先以OPTIONS方法发送一个预检请求,并设置相应的请求头。
    • 后端需要给[use::预检]请求响应对应的响应头,这样才算预检通过
  • 预检通过之后,前后端就可以直接进行跨域通信
    • 而不用设置其他响应头和请求头

实例

比如 Client 发送如下请求:

new Request().send('http://localhost:8080/server/options',{
    method: 'POST',
    header: {
        'Content-Type': 'application/json'  //告诉服务器实际发送的数据类型
    },
    success: function(data){
        document.write(data)
    }
});

Server 端处理请求的 controller:

@Controller
@RequestMapping("/server")
public class CorsController {
    @RequestMapping(value="/options", method= RequestMethod.POST)
    @ResponseBody
    public String options(HttpServletRequest request) throws Exception{
        return "SUCCESS";
    }
}

浏览器会以 OPTIONS 方法发出一个预检请求,浏览器会在请求头中加入:

Access-Control-Request-Headers:content-type
Access-Control-Request-Method:POST

这个预检请求的作用在这里就是告诉服务器:我会在后面请求的请求头中以 POST 方法发送数据类型是application/json 的请求,询问服务器是否允许。

Server端过滤器中添加Access-Control-Allow-Headers:

public class CrossDomainFilter implements Filter{
    public void init(FilterConfig filterConfig) throws ServletException {}
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse resp = (HttpServletResponse)servletResponse;
        resp.setHeader("Access-Control-Allow-Origin", "http://localhost:8000");
        resp.setHeader("Access-Control-Allow-Headers", "Content-Type");
        filterChain.doFilter(servletRequest,servletResponse);
    }
    public void destroy() {}
}

再来看请求的具体信息,第一次以 OPTIONS 方法发送预检请求,浏览器设置请求头:

Access-Control-Request-Headers:content-type //请求中加入的请求头
Access-Control-Request-Method:POST  //跨域请求的方法

服务端设置响应头:

Access-Control-Allow-Headers:Content-Type   //允许的header
Access-Control-Allow-Origin:http://localhost:8000 //允许跨域的源

可以看到 POST 请求成功了,第二次请求头中没有设置 Access-Control-Request-Headers 和 Access-Control-Request-Method。 但是这里有个问题,需要预检请求时,浏览器会发出两次请求,一次 OPTIONS,一次 POST。两次都返回了数据。这样服务端如果逻辑复杂一些,比如去数据库查找数据,从 web 层、 service 到数据库这段逻辑就会走两遍,浏览器会两次拿到相同的数据,所以服务端的 filter 可以改一下,如果是 OPTIONS 请求,在设置完跨域请求响应头后就不走后面的逻辑直接返回。

public class CrossDomainFilter implements Filter{
    public void init(FilterConfig filterConfig) throws ServletException {}
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse resp = (HttpServletResponse)servletResponse;
        resp.setHeader("Access-Control-Allow-Origin", "http://localhost:8000");
        resp.setHeader("Access-Control-Allow-Headers", "Content-Type");   
        //OPTION请求就直接返回
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        if (req.getMethod().equals("OPTIONS")) {
            resp.setStatus(200);
            resp.flushBuffer();
        }else {
            filterChain.doFilter(servletRequest,servletResponse);
        }
    }
    public void destroy() {}
}

详细教程

一文弄懂 CORS 跨域(前端+后端代码实例讲解) - 知乎