目录

跨域资源共享CORS

详细代码及测试案例见ref。

跨域错误产生的条件

  1. 必须是浏览器上发出的请求
  2. 必须是XMLHttpRequest请求
  3. 跨域: 协议,域名,端口任何一个不同就算跨域

解决思路

从三个产生条件上进行解决

  1. 针对浏览器

    以chrome为例,增加参数–disable-web-security

  2. 针对XMLHttpRequest请求

    使用jsonp

    Spring 4 中可以通过 AbstractJsonpResponseBodyAdvice 支持 jsonp;
    Spring 5 中已经移除了相关支持;

  3. 针对跨域

    1. 服务器返回支持跨域信息

      //在class上或service方法上使用@CrossOrigin

      1
      2
      3
      4
      5
      
      @RestController
      @CrossOrigin
      public class TestController {
         //...
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      
      @RestController
      public class Controller {
          @CrossOrigin
          @GetMapping("/show")
          public Banner getBanner() {
             //...
          }
      }
      
    2. 使用反向代理(具体见ref)

      使用反向代理,代理非本域名的请求,在外面看来就是同一个系统的请求,自然不用担心跨域问题。

      以nginx配置为例,配置非常简单,配置如下: 表示 /bcom 开头的请求都转发到 http://b.com:8080/

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      
       server {
              listen       80;
              server_name  a.com;
             
          location / {
              proxy_pass http://a.com:8080/;
              }
             
              location /bcom/ {
              proxy_pass http://b.com:8080/;
              }
      }
      

带cookie的跨域请求

默认跨域都是不带cookie或身份认证信息等的。

但我们很多时候需要发送cookie(如会话等),这种情况发送XMLHttpRequest请求的时候,客户端需要设置 withCredentials 为true,然后服务端需要返回支持cookie配置,需要返回 Access-Control-Allow-Credentials : trueAccess-Control-Allow-Origin : 对应的域名 ,注意:此处不能用*,必须是具体的域名。

编写js代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function getWithCookie() {
	$.ajax({
		type : "GET",
		url : "http://b.com:8080/getWithCookie",
		xhrFields : {
			withCredentials : true
		},
		success : function(data) {
			console.log("getWithCookie Loaded: ", data);
		}
	})
}

编写java代码,后台使用spring的@CookieValue注解获取cookie值。

1
2
3
4
5
6
7
8
@RestController
public class Controller {
    @GetMapping("/getWithCookie")
    public ResultBean<String> getWithCookie(@CookieValue(required=false) String cookie1) {
        System.out.println("\n-------TestController.getWithCookie(), cookie1="+cookie1);
        return new ResultBean<String>("getWithCookie ok, cookie1="+cookie1);
    }
}

注意,@CrossOrigin(allowedHeaders = { “X-Custom-Header1”, “X-Custom-Header2”, “X-Custom-Header4” })需要配置在方法上,不要配在类上面的 @CrossOrigin 注解上,否则会导致一些问题。

编写js代码,JQ里面增加自定义头有2种方法。headers 和 beforeSend事件 加。

带自定义header的跨域请求

很多时候,我们需要发送自定义的header,这个时候首先先要在服务器配置能接受哪些header。并使用 @RequestHeader 得到头字段。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@RestController
public class Controller {
    @GetMapping("/getWithHeader")
    @CrossOrigin(allowedHeaders = { "X-Custom-Header1", "X-Custom-Header2", "X-Custom-Header4" })
    public ResultBean<String> getWithHeader(
            @RequestHeader(required = false, name = "X-Custom-Header1") String header1) {
        System.out.println("\n-------TestController.getWithHeader(), header1=" + header1);
        return new ResultBean<String>("getWithHeader ok, header1=" + header1);
    }
}

总结

本着工匠精神就写细一点,发现东西还是比较多的,本来觉得写4个小时应该就能写完了,结果周末花了快2天才写完,最后总结一下,对工作中用得上的知识点。

  • 发生跨域访问的三个条件:浏览器端,跨域,异步。
  • 针对异步的解决方法jsonp有很多硬伤,并不推荐。
  • 浏览器发送跨域请求之前会区分简单请求还是非简单请求,简单请求是直接请求,请求完再根据响应头信息判断(如果不支持跨域,尽管服务器成功执行返回200,但浏览器还是报错),非简单请求会先发送 OPTIONS咨询命令(如果不支持跨域,返回403禁止访问错误,支持则返回200,但并不一定就代表该请求能发出去,某些情况服务器还需要额外判断)。
  • 工作中遇到比较常见的非简单请求就是发送json数据的和带自定义头的。(带cookie的是简单请求)
  • 使用Spring的 @CrossOrigin 能很方便的解决跨域访问问题,几乎只需要一行代码。
  • 使用反向代理也是比较好的解决方法,公司内部配置也比较简单,反向代理能封装很多细节,增加很多其他特性。
  • 学会注解 @RequestHeader 和 @CookieValue 的使用,不要自己去request对象上获取这些信息。

相关问题

jsonp为什么只支持get,不支持post?

jsonp不是使用xhr发送的,是使用动态插入script标签实现的,当前无法指定请求的method,只能是get。调用的地方看着一样,实际上和普通的ajax有2点明显差异:1. 不是使用xhr 2.服务器返回的不是json数据,而是js代码。

/images/202004/2020年4月9日13点01分1.png

spring boot 跨域实现

https://spring.io/guides/gs/rest-service-cors/

spring cloud 跨域实现

https://segmentfault.com/a/1190000017188296 https://cloud.spring.io/spring-cloud-gateway/multi/multi__cors_configuration.html

References