一、概述
我们在开发过程中经常会遇到前后端分离而导致的跨域问题,导致无法获取返回结果。跨域就像分离前端和后端的一道鸿沟,君在这边,她在那边,两两不能往来。
①、什么是跨域
跨域(
CORS
)是指不同域名之间相互访问。指的是浏览器不能执行其他网站的脚本,它是由浏览器的同源策略所造成的,是浏览器对于JavaScript
所定义的安全限制策略。
②、什么情况会跨域
- 同一协议, 如
http
或https
- 同一
IP
地址, 如127.0.0.1
- 同一端口, 如
8080
以上三个条件中有一个条件不同就会产生跨域问题。
③、为什么要设计同源策略
- 其中一个重要原因就是对
cookie
的保护,cookie
中存着sessionID
。黑客一旦获取了sessionID
并且在有效期内 就可以登录,这里我们可以简单的认为sessionID
全等于 账户加密码,想想当我们访问了一个恶意网站,如果没有同源策略,那么这个网站就能通过js
访问document.cookie
得到用户关于的各个网站的sessionID
其中可能有银行网站等等 - 通过已经建立好的
session
连接进行攻击的 这里有一个专有名词 美名曰CSRF
攻击 ,需要注意的是同源策略无法完全防御CSRF
这里需要服务端配合
再举个例子,现在我扮演坏人,我通过一个
iframe
加载某宝的登录页面,等傻傻的用户登录我的网站的时候,我就把这个页面弹出,用户一看,阿里唉大公司肯定安全,就屁颠屁颠的输入了密码注意如果没有同源策略,我这个恶意网站就能通过dom
操作获取到用户输入的值,从而控制该账户所以同源策略是绝对必要的。
二、解决方案
①、准备工作
准备两个
SpringBoot
Module
在
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
,正常访问
通过
CrossDomainA
去访问CrossDomainB
呢
②、添加响应头解决跨域
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");
}
}
实际上
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>
④、Nginx反向代理
没有安装
Nginx
的童鞋戳这里下载,速度较快,安装完毕后进入解压目录双击启动nginx.exe
即可
访问
127.0.0.1:80
验证是否启动成功
Nginx
是一个静态服务器,所以把我们刚才写的静态Html
页面放在里面再适合不过了,进入Nginx/html
目录,可以将里面的文件删掉,将刚才那个静态Html
页面粘贴进去
然后刷新一个
127.0.0.1:80
就可以看到生效了
修改访问路径,将
8080
改为80
进入
Nginx/conf
目录,修改nginx.conf
文件
location /api {
proxy_pass http://127.0.0.1:8081;
}
然后重启一下项目
最后还有一步就是
CrossDomainB
的访问路径保持和配置文件配置的路径一直,也就是访问路径加上一个api
@RequestMapping("/api/user")
最后访问测试就成功啦
⑤、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')";
}
}
三、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);
}
}