Skip to content

跨域常见解决方案 #13

@kailbin

Description

@kailbin

同源

同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能。以下几点同时满足认为是同源:

  • 协议(http 与 https 不同源)
  • 域名 (10.10.3.33、localhost、blog.kail.xyz、kail.xyz 都不同源)
  • 端口 (80、8080 不同源)

同源策略限制:从一个域上加载的脚本不允许访问另外一个域的文档属性

但是以下标签通过 src 加载资源(实际上是发起了一次GET请求),不受同源策略的限制,可以跨域访问资源:

  • <script>
  • <img>
  • <iframe>
  • <link>

但浏览器限制了JavaScript的权限使其不能读、写加载的内容。

JSONP(JSON with Padding)

AJAX 受到同远策略的限制,无法跨域访问资源。

但是人们利用<script>标签的跨域能力实现了跨域数据的访问,产生了 JSONP。需要注意的是 JSONP 和 AJAX 并不是相同的技术概念,两者完全不同。

使用 JSOUP 实现跨域的原理与流程如下:

  • js在浏览器端生成一个 function,方法名随机,如:
eval("function hello_1232412342(data){console.info('hello ' + data);}")

这时候浏览器端会存在一个方法,如下:

function hello_1232412342(data){
    console.info('hello ' + data);
}
  • js在浏览器端 document 中写入 <script> 标签,加载服务端接口,可以非同源,需要附加参数,callback=hello_1232412342,callback 的参数值是上一步生成的方法名
<script src="http://other.host.com/some/resource?callback=hello_1232412342"><script>
  • 服务端接口会获取 callback 参数值,对返回数据进行包装,hello_1232412342("world")
  • 浏览器解析到 <script> 标签会执行里面的脚本

整个过程实际上会简单的变成这样:

<script>
<!-- 定义一个名称随机的函数,该函数是通过eval() 生成的 -->
function hello_1232412342(data){
    console.info('hello ' + data);
}

<!-- 执行这个函数,这个函数是服务端生成的 -->
<!-- 通过script标签加载进来: <script src="http://other.host.com/some/resource?callback=hello_1232412342"><script> -->
hello_1232412342("world");
<script>

JSONP 的缺点

  • JSONP 错误的处理不完善;如果动态脚本插入有效,就执行调用;如果无效,就静默失败。失败是没有任何提示的。
  • JSONP 被不信任的服务使用时会很危险;服务端对 JSONP 进行支持后,就意味着其它不受信任的网站可以异步加载及的资源
  • 只支持 GET 类型的请求

AJAX 跨域请求 - JSONP获取JSON数据
jQuery使用JSONP时的错误处理

Proxy

Proxy 解决跨域问题是利用了服务端之间的资源请求不会有跨域限制的特点实现的,具体来说就是前端发起的请求被Nginx拦截,再由Nginx代由转发请求到资源服务器请求资源。

Nginx 配置如下:

# 拦截以 /cors/ 开头的链接
location ^~ /cors/ {
   
    # 设置 proxy_hostname 变量,默认访问本机 55582 端口
    set $proxy_hostname http://192.168.31.105:55582;
    
    # 如果请求头中有 Target-Origin 且以 http 开头
    # 则转发的指定的目标源
    if ($http_target_origin ~ ^http ) {
        
        set $proxy_hostname $http_target_origin;
        # add_header 'Cors-Target' "${http_target_origin}";
    }

    proxy_pass $proxy_hostname;
}

前端(在http://192.168.31.105源下,调用 http://192.168.31.105:55581/cors/data)调用示例如下:

<script src="//cdn.bootcss.com/jquery/1.12.0/jquery.min.js"></script>
<script>
$(function () {
    $.ajax({
        type: "GET",
        url: "http://192.168.31.105/cors/data",
        success: function(data) {
            $("body").append(data);
        },
        beforeSend: function(xhr) {
            // 自定义的请求头
            // 访问 http://192.168.31.105/cors/data 到Nginx之后,会转发到 http://192.168.31.105:55581/cors/data
            xhr.setRequestHeader("Target-Origin", "http://192.168.31.105:55581");
        }
    });
})
</script>

nginx配置location总结及rewrite规则写法
ajax中的setRequestHeader设置请求头

CORS

CORS 全称是"跨域资源共享"(Cross-origin resource sharing),它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

浏览器通过读取 Response 的响应头来控制AJAX是否可以跨域。

// 简单请求 Response
Access-Control-Allow-Origin: *          // 允许所有Origin
Access-Control-Allow-Credentials: true  // true 表示可以发送Cookies
Access-Control-Expose-Headers: FooBar   // 允许跨域读取FooBar扩展字段

// 预检请求 Request
Access-Control-Request-Method: PUT      // 允许发送的请求类型
Access-Control-Request-Headers: X-Cus-H // 允许发送的自定义头

// 预检请求 Response
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Cus-H   // 允许发送的自定义头
Access-Control-Max-Age: 1728000         // 单位s,在此期间,不用发出另一条预检请求

Nginx 支持

location / {
  #
  # 对复杂请求,预检请求进行处理
  if ($request_method = 'OPTIONS') {
    add_header 'Access-Control-Allow-Origin' '*';
    #
    # 允许Cookie 和 允许的请求方式
    add_header 'Access-Control-Allow-Credentials' 'true';
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    #
    # 允许发送的头
    add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
    #
    # 告诉客户端与检查有效期20天
    add_header 'Access-Control-Max-Age' 1728000;
    add_header 'Content-Type' 'text/plain charset=UTF-8';
    add_header 'Content-Length' 0;
    return 204;
  }
  
  
  if ($request_method = 'POST') {
    # 允许所有(建议配置具体的域名)
    add_header 'Access-Control-Allow-Origin' '*';
    add_header 'Access-Control-Allow-Credentials' 'true';
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
  }
  
  
  if ($request_method = 'GET') {
    add_header 'Access-Control-Allow-Origin' '*';
    add_header 'Access-Control-Allow-Credentials' 'true';
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
  }
}

Open nginx CORS configuration
Nginx CORS实现JS跨域

Spring MVC 支持

Spring MVC 4.2 开始对 CORS 进行支持,最简单的方式就是使用注解@CrossOrigin,可以控制到可以一个请求 或 一个Controller。

以下注解里面的属性可以和W3C定义的头属性相对应:

// Access-Control-Allow-Origin
String[] origins() default {};

// Access-Control-Allow-Headers 允许发送的自定义头
String[] allowedHeaders() default {};

// Access-Control-Expose-Headers 允许跨域读取的扩展字段
String[] exposedHeaders() default {};

// Access-Control-Allow-Methods 允许的请求方式
RequestMethod[] methods() default {};

// Access-Control-Allow-Credentials
String allowCredentials() default "";

// Access-Control-Max-Age
long maxAge() default -1;

Enabling Cross Origin Requests for a RESTful Web Service
CORS support in Spring Framework
官方文档 CORS Support

CORS 缺点

  • IE浏览器不能低于IE10

拓展阅读

跨域资源共享 CORS 详解
HTTP访问控制(CORS)
Cross-Origin Resource Sharing (CORS)
Same-origin policy
Nginx反向代理、CORS、JSONP等跨域请求解决方法总结

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions