一、概述

我们在开发过程中经常会遇到前后端分离而导致的跨域问题,导致无法获取返回结果。跨域就像分离前端和后端的一道鸿沟,君在这边,她在那边,两两不能往来。

①、什么是跨域

跨域(CORS)是指不同域名之间相互访问。指的是浏览器不能执行其他网站的脚本,它是由浏览器的同源策略所造成的,是浏览器对于JavaScript所定义的安全限制策略。

②、什么情况会跨域

  • 同一协议, 如httphttps
  • 同一IP地址, 如127.0.0.1
  • 同一端口, 如8080

以上三个条件中有一个条件不同就会产生跨域问题。

③、为什么要设计同源策略

  • 其中一个重要原因就是对cookie的保护,cookie 中存着sessionID 。黑客一旦获取了sessionID 并且在有效期内 就可以登录,这里我们可以简单的认为sessionID 全等于 账户加密码,想想当我们访问了一个恶意网站,如果没有同源策略,那么这个网站就能通过js 访问document.cookie 得到用户关于的各个网站的sessionID 其中可能有银行网站等等
  • 通过已经建立好的session连接进行攻击的 这里有一个专有名词 美名曰 CSRF 攻击 ,需要注意的是同源策略无法完全防御CSRF 这里需要服务端配合

再举个例子,现在我扮演坏人,我通过一个iframe 加载某宝的登录页面,等傻傻的用户登录我的网站的时候,我就把这个页面弹出,用户一看,阿里唉大公司肯定安全,就屁颠屁颠的输入了密码注意如果没有同源策略,我这个恶意网站就能通过dom操作获取到用户输入的值,从而控制该账户所以同源策略是绝对必要的。

二、解决方案

①、准备工作

准备两个SpringBoot Module

为什么 sessionID无法跨域 session跨域是什么意思_spring boot

CrossDomainA中新增一个HTML页面,用于访问CrossDomainB下的user请求

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Tile</title>
</head>
<body>
<button id="button">访问CrossDomainB的/user</button>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>
    $("#button").click(function () {
        $.get('http://localhost:8081/user', function (data) {
            console.log(data)
        })
    })
</script>
</body>
</html>

CrossDomainB中新增一个User实体类和UserController控制器

/**
 * @author PengHuAnZhi
 * @ProjectName CrossDomain
 * @Description TODO
 * @time 2021/9/28 10:51
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private String userName;
    private String password;
}
/**
 * @author PengHuAnZhi
 * @ProjectName CrossDomain
 * @Description TODO
 * @time 2021/9/28 10:49
 */
@RestController
public class UserController {
    @RequestMapping("/user")
    public User getUser(HttpServletResponse response) {
        return new User("彭焕智", "666");
    }
}

其中CrossDomainB监听8081端口,CrossDomainA监听8080端口,启动项目,访问8081,正常访问

为什么 sessionID无法跨域 session跨域是什么意思_javascript_02

通过CrossDomainA去访问CrossDomainB

为什么 sessionID无法跨域 session跨域是什么意思_javascript_03

②、添加响应头解决跨域

IE10以下不支持

response新增一个header

/**
 * @author PengHuAnZhi
 * @ProjectName CrossDomain
 * @Description TODO
 * @time 2021/9/28 10:49
 */
@RestController
public class UserController {
    @RequestMapping("/user")
    public User getUser(HttpServletResponse response) {
        //Access-Control-Allow-Origin允许跨域,*表示允许所有人都允许跨域,也可以指定主机
        response.setHeader("Access-Control-Allow-Origin","http://localhost:8080");
        return new User("彭焕智", "666");
    }
}

为什么 sessionID无法跨域 session跨域是什么意思_javascript_04

实际上CrossDomainA去访问CrossDomainB的时候发送的是两个请求,第一个是option类型的域检请求,询问CrossDomainB服务器是否支持跨域,如果支持,再发送一个get请求给userController,否则第二个请求就不发了

  • 对于域检请求,他是有一个时间控制的,不是每一次跨域请求都需要进行域检请求,我们可以设置一个时长,当上一次跨域请求到这一次跨域请求间隔时间超出了这个时长,那么再重新发送一个域检请求,否则就直接发送get而不发送域检请求
response.setHeader("Access-Control-Allow-Origin","http://localhost:8080");
response.setHeader("Access-Control-Max-Age","10");

除了可以控制跨域请求和域检请求间隔时长,还可以设置允许的请求方法,携带的headers等等

Access-Control-Allow-Method
Access-Control-Allow-Headers
...

对于SpringBoot中,直接就提供了支持跨域的注解@CrossOrigin

/**
 * @author PengHuAnZhi
 * @ProjectName CrossDomain
 * @Description TODO
 * @time 2021/9/28 10:49
 */
@RestController
public class UserController {
    @RequestMapping("/user")
    @CrossOrigin(origins = {"*"}, maxAge = 3600, methods = {RequestMethod.GET, RequestMethod.POST})
    public User getUser(HttpServletResponse response) {
        return new User("彭焕智", "666");
    }
}

③、手写Java反向代理

原理就是,我们通过CrossDomainA去访问CrossDomainB的时候,让CrossDomainA生成一个我们用户自己的一个代理,通过这个代理再去访问CrossDomainB,就好比我们自己去访问CrossDomainB一样,相当于是服务器和服务器之间去请求了,不再是通过浏览器访问,代理收到响应后,再把响应返回给用户

package com.phz.cross_domain.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * @author PengHuAnZhi
 * @ProjectName CrossDomain
 * @Description TODO
 * @time 2021/9/28 14:16
 */
@RestController
public class ProxyController {
    @Value("${proxy.address}")
    //proxy.address=http://127.0.0.1:8081
    private String proxyAddress;

    @Resource
    private RestTemplate restTemplate;

    @RequestMapping("/api/**")
    @ResponseBody
    public Object proxy(HttpServletRequest request) {
        System.out.println(proxyAddress);
        return restTemplate.getForObject(proxyAddress + request.getRequestURI().replace("/api", ""), Object.class);
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Tile</title>
</head>
<body>
<button id="button">访问CrossDomainB的/user</button>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>
    $("#button").click(function () {
        $.get('http://localhost:8080/api/user', function (data) {
            console.log(data)
        })
    })
</script>
</body>
</html>

为什么 sessionID无法跨域 session跨域是什么意思_为什么 sessionID无法跨域_05

④、Nginx反向代理

没有安装Nginx的童鞋戳这里下载,速度较快,安装完毕后进入解压目录双击启动nginx.exe即可

为什么 sessionID无法跨域 session跨域是什么意思_node.js_06

访问127.0.0.1:80验证是否启动成功

为什么 sessionID无法跨域 session跨域是什么意思_spring boot_07

Nginx是一个静态服务器,所以把我们刚才写的静态Html页面放在里面再适合不过了,进入Nginx/html目录,可以将里面的文件删掉,将刚才那个静态Html页面粘贴进去

为什么 sessionID无法跨域 session跨域是什么意思_javascript_08

然后刷新一个127.0.0.1:80就可以看到生效了

为什么 sessionID无法跨域 session跨域是什么意思_html5_09

修改访问路径,将8080改为80

为什么 sessionID无法跨域 session跨域是什么意思_node.js_10

进入Nginx/conf目录,修改nginx.conf文件

location /api {
	proxy_pass http://127.0.0.1:8081;
}

为什么 sessionID无法跨域 session跨域是什么意思_html5_11

然后重启一下项目

为什么 sessionID无法跨域 session跨域是什么意思_node.js_12

最后还有一步就是CrossDomainB的访问路径保持和配置文件配置的路径一直,也就是访问路径加上一个api

@RequestMapping("/api/user")

最后访问测试就成功啦

为什么 sessionID无法跨域 session跨域是什么意思_node.js_13

⑤、JsonP

这种方式就是利用了浏览器中某些标签天然支持跨域,比如Script

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Tile</title>
</head>
<body>
<button id="button">访问CrossDomainB的/user</button>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>
    $("#button").click(function () {
        $.get('http://localhost:8080/api/user', function (data) {
            console.log(data)
        })
    })

    function cross(data) {
        console.log(data);
    }
</script>
<script src="http://127.0.0.1:8081/cross"></script>
</body>
</html>
/**
 * @author PengHuAnZhi
 * @ProjectName CrossDomain
 * @Description TODO
 * @time 2021/9/28 10:49
 */
@RestController
public class UserController {
    @RequestMapping("/api/user")
    @CrossOrigin(origins = {"*"}, maxAge = 3600, methods = {RequestMethod.GET, RequestMethod.POST})
    public User getUser(HttpServletResponse response) {
        return new User("彭焕智", "666");
    }

    @RequestMapping("/cross")
    public String cross() {
        return "cross('hello')";
    }
}

为什么 sessionID无法跨域 session跨域是什么意思_javascript_14

三、SpringBoot处理跨域问题

不难,注入一个WebMvcConfigure的实现类,重写一个addCorsMappings方法即可,具体配置理解不难

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
            .allowedOrigins("*")
            .allowCredentials(true)
            .allowedMethods("GET", "POST", "DELETE", "PUT","OPTIONS")
            .maxAge(3600);
    }
}