# AJAX 和跨域

# 1. 跨域问题原由

问题

什么是 ajax 跨域问题?

简单来说,就是前台调用后台接口的时候,由于『浏览器』的同源策略(Same-origin Policy)过于严格,如果这个接口不是同一个域的,就会产生跨域问题。

问题

什么是同源请求,什么是跨域请求?

所谓同源是指:域名、协议、端口相同。

如果发出去的请求不是本域的,协议域名端口,任何一个不一样,浏览器就认为是跨域的。

URL 说明 是否跨域
http://www.a.com/a.js
http://www.a.com/b.js
同一域名 没有跨域
http://www.a.com/a/a.js
http://www.a.com/b/b.js
同一域名下的不同文件夹 没有跨域
http://www.a.com/a.js
http://www.a.com:8080/b.js
端口不同 跨域
http://www.a.com/a.js
https://www.a.com/b.js
协议不同 跨域
http://www.a.com/a.js
http://70.32.97.74/b.js
域名和域名的 IP 跨域
http://www.a.com/a.js
http://xxx.a.com/a.js
http://yyy.a.com/b.js
二级域名不同 跨域(此时,cookie 也不会共享)
http://www.a.com/a.js
http://www.b.com/b.js
域名不同 跨域

需要注意的是:发送的是 xhr(XMLHTTPRequest)请求才会产生跨域问题。如果发出去的请求不是 XHR 请求的话,即使跨域,浏览器也不会报错。

# 2. CORS 之前的跨域问题的解决办法

# 方案一:关闭浏览器的跨域限制功能

这是最简单粗暴,也是最不可能采用的方案。

# 方案二:JSONP(JSON with Padding)

JSONP 方案是一个早期方案,也是一个非标准方案。不建议使用。

它的解题思路是,既然只有发送的是 xhr(XMLHTTPRequest)请求才会有可能产生跨域问题(请求被浏览器拦截/阻止),那么我们就想办法 把 XHR 请求改为非 XHR 请求 。只要不是 XHR 请求,浏览器就不会报跨域预警,就会放行请求。

由于该方案是一个早期的非标准方案,且存在若干缺点,因此慢慢被后续的 CORS 所取代。此处不作讲解。

# 3. 现行的标准的方案:CORS

CORS(Cross-origin resource sharing,跨域资源共享)是一个 W3C 标准,定义了在必须访问跨域资源时,浏览器与服务器应该如何沟通。

简单来说,就是浏览器『一刀切式地阻止一切跨域请求』过于的粗暴,因此,CORS 的方案就是『让服务器决定是否响应这个跨域请求』。服务器不响应,浏览器就阻止显示响应结果;服务器愿意响应,浏览器就放行,展示响应结果。

TIP

整个 CORS 通信过程,都是浏览器自动完成(基本上目前所有的浏览器都实现了 CORS 标准),不需要用户参与。对于开发者来说,CORS 通信与同源的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但『整个过程用户不会有感觉』。

浏览器首先会发起一个请求方法为 OPTIONS 的『预检请求』,用于询问/确认浏服务器是否允许跨域请求,只有在得到服务器的许可后才会发出实际请求。

# 4. Spring Boot 对 CORS 的支持

# 环境准备

首先是支持 Restful 的 Controller,这里就不使用数据库了,简单一点。

点击查看代码 UserController
@RestController
public class UserController {

  /**
   * 查询用户列表
   */
  @GetMapping(value = "/users")
  public ResponseEntity<Map<String, Object>> getUserList() {
    Map<String, Object> map = new HashMap<>();
    List<User> userList = new ArrayList<>();
    userList.add(new User(1, "tommy", 20));
    userList.add(new User(2, "jerry", 19));
    map.put("result", userList);
    map.put("status", 200);

    return ResponseEntity.ok(map);
  }
}
点击查看代码 User
public class User {
private int id;
private String username;
private int age;

  // getter / setter
}
点击查看代码 页面

不是使用 IDEA 的用户,自己找个 tomcat 启动 test.html 页面,修改端口为其他。是 IDEA 的用户在 test.html 页面源码上鼠标右键选择某个浏览器打开(这里用到了 IDEA 内置服务器的功能)

<!DOCTYPE html>
<html lang="en"> 
    <head> 
    <meta charset="UTF-8"> 
    <title>Title</title> 
    <script src="https://cdn.bootcss.com/jquery/2.1.0/jquery.min.js"></script> 
    <script type="text/javascript">
      function crosRequest(){
        $.ajax({
          url:'http://localhost:8080/users',
          type: 'get',
          dataType: 'json',
          success: function(data){
            console.log(data);
          }
        });
      }
    </script> 
  </head> 
  <body> 
    <button onclick="crosRequest()">请求跨域资源</button> 
  </body> 
</html>

# CORS 配置方案一:@CrossOrigin 注解

在请求处理方法,或者是在 Controller 类上标注 @CrossOrigin 注解,表示本方法,或本类中的所有方法接收来自【远方】的跨域请求。

@CrossOrigin
@GetMapping(value = "/users")
public ResponseEntity<Map<String, Object>> getUserList() {
    ...
}

@CrossOrigin 注解的属性的默认值是:

"Access-Control-Allow-Origin" : "*"
"Access-Control-Allow-Methods" : "GET,POST,PUT,OPTIONS"
"Access-Control-Allow-Credentials" : "true"

如果有需要的话,你可以通过注解的属性进行手动指定。例如:

@CrossOrigin(
    origins = { "http://domain.com", "http://domain2.com", "http://localhost:63342" }, 
    methods = { RequestMethod.GET, RequestMethod.POST }, 
    allowCredentials = "true")

# 方案二:全局 CORS 配置

如果使用的是 Java 代码配置,那么可以在配置类中进行统一配置。

@Bean 
public WebMvcConfigurer corsConfigurer() {

  return new WebMvcConfigurer() {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
      registry.addMapping("/**")
        .allowedOrigins("http://domain.com", "http://domain2.com", "http://localhost:63342")
        .allowedMethods("GET", "POST", "DELETE", "PUT", "OPTIONS")
        .allowCredentials(false);
    }
  };

}

『完』