Spring Boot 使用 CORS 解决跨域请求问题

对于前后端分离的项目来说,如果前端项目与后端项目部署在两个不同的域下,那么势必会引起跨域问题的出现。

那什么是跨域呢?

跨域指的是从一个域名去请求另外一个域名的资源。即跨域名请求,跨域时,浏览器不能执行其他域名网站的脚本,是由浏览器的 “同源策略” 造成的,是浏览器施加的安全限制。跨域的严格一点来说就是只要协议,域名,端口有任何一个的不同,就被当作是跨域。

下面举个例子:

判断下面 URL 是否和 http://www.baidu.com/a/a.html 同源

  • http://www.baidu.com/b/b.html 同源
  • http://www.baidu2.com/a/a.html 不同源,域名不相同
  • https://www.baidu.com/b/b.html 不同源,协议不相同
  • http://www.baidu.com:8080/b/b.html 不同源,端口号不相同

综上所述,在同源策略的限制下,非同源的网站之间不能发送 AJAX 请求。如有需要,可通过降域或其他技术实现。

下面实现一个案例来验证一下

案例

package com.example.demo.controller;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("server")
public class TestController {
	
	@RequestMapping("test")
	public String test(HttpServletResponse httpResponse,HttpSession session){
		return "阿三";
	}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
	<script type="text/javascript" src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
	<div id="msg">
	</div>
	<script type="text/javascript">
		$(document).ready(function(){
			$.ajax({
				url:'http://localhost:8090/server/test',
				type:'get',
				dataType:'json',
				success:function(res){
					console.log(res);
					$("#msg").html(res.msg);
				},
				error:function(){
					console.log("error");
				}
			})
		});
	</script>
	
</body>
</html>

在这里插入图片描述
由于两项目的端口不通,所以造成了跨域访问。

那么如何解决跨域问题?,跨域问题解决方案有很多,本文主要讲通过 CORS 协议解决跨域问题。

CORS简介

CORS 是一个 W3C 标准,全称是 "跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源(协议 + 域名 + 端口)服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。CORS 需要浏览器和服务器同时支持。它的通信过程,都是浏览器自动完成,不需要用户参与。

对于开发者来说,CORS 通信与同源的 AJAX/Fetch 通信没有差别,代码完全一样。浏览器一旦发现请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。

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

简单请求

在 CORS 出现前,发送 HTTP 请求时在头信息中不能包含任何自定义字段,且 HTTP 头信息不超过以下几个字段:

Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type(值限于 3 个值:application/x-www-form-urlencoded、multipart/form-data、text/plain)

这里给出一个例子:

GET /baidu HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, sdch, br
Origin: http://www.baidu.com
Host: www.baidu.com

对于简单请求,CORS 的策略是请求时在请求头中增加一个 Origin 字段,服务器收到请求后,根据该字段判断是否允许该请求访问。

  • 允许:则在 HTTP 头信息中添加 Access-Control-Allow-Origin 字段,并返回正确的结果
  • 不允许:则不在 HTTP 头信息中添加 Access-Control-Allow-Origin 字段

非简单请求

对于非简单请求的跨源请求,浏览器会在真实请求发出前,增加一次 OPTION 请求,称为预检请求 (preflight request) 。预检请求将真实请求的信息,包括请求方法、自定义头字段、源信息添加到 HTTP 头信息字段中,询问服务器是否允许这样的操作。

下面的请求会触发预检请求:

  • 使用了 PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH方法
  • 人为设置了非规定内的其他首部字段,参考上面简单请求的安全字段集合,还要特别注意 Content-Type 的类型
  • XMLHttpRequestUpload 对象注册了任何事件监听器
  • 请求中使用了 ReadableStream 对象

以下是一个发起预检请求的例子:

发起请求的 origin 与请求的服务器的 host 不同,而且根据上面的条件判断,触发了预检

Spring Boot 使用 CORS 解决跨域请求问题_第1张图片
Spring Boot 使用 CORS 解决跨域请求问题_第2张图片
这里说一下这几个的含义:
在这里插入图片描述

  • Access-Control-Allow-Origin 该字段必填。它的值要么是请求时Origin字段的具体值,要么是一个*,表示接受任意域名的请求
  • Access-Control-Allow-Methods 该字段必填。它的值是逗号分隔的一个具体的字符串或者*,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求
  • Access-Control-Expose-Headers 该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定
  • Access-Control-Allow-Credentials 该字段可选。它的值是一个布尔值,表示是否允许发送Cookie.默认情况下,不发生Cookie,即:false。对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json,这个值只能设为true。如果服务器不要浏览器发送Cookie,删除该字段即可
  • Access-Control-Max-Age 该字段可选,用来指定本次预检请求的有效期,单位为秒。在有效期间,不用发出另一条预检请求
  • 当预检请求通过后,浏览器才会发送真实请求到服务器。这样就实现了跨域资源的请求访问。

    Spring Boot CORS 实现

    spring mvc 4.2 版本增加了对 CORS 的支持,通过 Spring Boot 可以非常简单的实现跨域访问。

    解决办法:

    CORS 全局配置-实现WebMvcConfigurer

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.CorsRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    @Configuration
    public class CorsConfig implements WebMvcConfigurer {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowedOrigins("*")
                    .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                    .allowCredentials(true)
                    .maxAge(3600)
                    .allowedHeaders("*");
        }
    
    

    这种方式是全局配置的,但是很多都是基于旧的 Spring 版本,比如 WebMvcConfigurerAdapter 在 Spring5.0 已经被标记为 Deprecated,我们通过看源码就可以知道:

    /**
     * An implementation of {@link WebMvcConfigurer} with empty methods allowing
     * subclasses to override only the methods they're interested in.
     *
     * @author Rossen Stoyanchev
     * @since 3.1
     * @deprecated as of 5.0 {@link WebMvcConfigurer} has default methods (made
     * possible by a Java 8 baseline) and can be implemented directly without the
     * need for this adapter
     */
    @Deprecated
    public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {}
    

    拦截器实现

    通过实现 Fiter 接口在请求中添加一些 Header 来解决跨域的问题

    import org.springframework.context.annotation.Configuration;
    import javax.servlet.*;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    @WebFilter(filterName = "CorsFilter ")
    @Configuration
    public class CorsFilter implements Filter {
        @Override
        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
            HttpServletResponse response = (HttpServletResponse) res;
            response.setHeader("Access-Control-Allow-Origin","*");
            response.setHeader("Access-Control-Allow-Credentials", "true");
            response.setHeader("Access-Control-Allow-Methods", "POST, GET, PATCH, DELETE, PUT");
            response.setHeader("Access-Control-Max-Age", "3600");
            response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
            chain.doFilter(req, res);
        }
    

    使用@CrossOrigin 注解

    在 Controller 上使用 @CrossOrigin 注解,该类下的所有接口都可以通过跨域访问。

    
    @RestController
    @RequestMapping("/test")
    //@CrossOrigin //所有域名均可访问该类下所有接口
    @CrossOrigin("https://www.baidu.com") // 只有指定域名可以访问该类下所有接口
    public class CorsTestController {
    
        @GetMapping("/test2")
        public String sayHello() {
            return "hello world";
        }
    }
    

    这里指定当前的 CorsTestController 中所有的方法可以处理 https://www.baidu.com 域上的请求。

    我们点击 CrossOrigin 进去源码看一下:

    @Target({ ElementType.METHOD, ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface CrossOrigin {
     
     
    }
    

    从注解 @Target 可以看出,注解可以放在 method、class 等上面,类似 RequestMapping,也就是说,整个Controller 下面的方法可以都受控制,也可以单个方法受控制。

    原理解析

    这里不管是通过哪种方式配置 CORS,都是在构造 CorsConfiguration,一个 CORS 配置用一个 CorsConfiguration类来表示,如下所示:

    Spring Boot 使用 CORS 解决跨域请求问题_第3张图片
    Spring 中对 CORS 规则的校验,通过委托给 DefaultCorsProcessor实现。

    处理流程如下:

    • 判断依据是 Header 中是否包含 Origin。如果包含则说明为 CORS 请求,转到 2;否则,说明不是 CORS 请求,不作任何处理
    • 判断 response 的 Header 是否已经包含 Access-Control-Allow-Origin,如果包含,证明已经被处理过了, 转到 3,否则不再处理
    • 判断是否同源,如果是则转交给负责该请求的类处理
    • 是否配置了 CORS 规则,如果没有配置,且是预检请求,则拒绝该请求,如果没有配置,且不是预检请求,则交给负责该请求的类处理。如果配置了,则对该请求进行校验

    校验就是根据 CorsConfiguration 这个类的配置进行判断:

    • 判断 origin 是否合法
    • 判断 method 是否合法
    • 判断 header是否合法
    • 如果全部合法,则在 response header 中添加响应的字段,并交给负责该请求的类处理,如果不合法,则拒绝该请求

    源码地址

    总结

    本文主要介绍了 CORS 的知识,以及如何在 Spring Boot 中配置 CORS。希望对小伙伴的学习有所帮助。

你可能感兴趣的:(java,spring,boot,java,前端)