1.跨域三种情况
在请求时,如果出现了以下情况中的任意一种,那么它就是跨域请求:
- 协议不同,如 http 和 https;
- 域名不同;
- 端口不同。
也就是说,即使域名相同,如果一个使用的是 http,另一个使用的是 https,那么它们也属于跨域访问。常见的跨域问题如下图所示:
2.跨域问题演示
接下来,我们使用两个 Spring Boot 项目来演示跨域的问题,其中一个是端口号为 8080 的前端项目,另一个端口号为 9090 的后端接口项目。
2.1 前端网站
前端项目只需要在 resources 下放两个文件,一个用于发送 ajax 请求的 jquery.js,另一个是 html 前端页面,工程目录如下图所示:
其中前端页面 index.html 的代码如下:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>跨域测试页面title>
<script src="js/jquery.min.js">script>
head>
<body>
<h1>跨域测试h1>
<div>
<input type="button" onclick="mySubmit()" value=" 发送跨域请求 ">
div>
<script>
function mySubmit() {
// 发送跨域请求
jQuery.ajax({
url: "http://localhost:9090/test",
type: "POST",
data: {"name": "Java"},
success: function (result) {
alert("返回数据:" + result.data);
}
});
}
script>
body>
html>
2.2 后端接口
后端接口项目首先先在 application.properties 配置文件中,设置项目的端口号为 9090,如下所示:
server.port=9090
然后创建一个后端控制器,返回一个 JSON 格式的数据,实现代码如下:
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
@RestController
public class TestController {
@RequestMapping("/test")
public HashMap<String, Object> test() {
return new HashMap<String, Object>() {{
put("state", 200);
put("data", "success");
put("msg", "");
}};
}
}
以上两个项目创建并启动成功之后,使用前端项目访问后端接口,因为端口不一样,所以也属于跨域访问,运行结果如下图所示:
3.解决跨域问题
在 Spring Boot 中跨域问题有很多种解决方案,比如以下 5 个:
- 使用 @CrossOrigin 注解实现跨域;
- 通过配置文件实现跨域;
- 通过 CorsFilter 对象实现跨域;
- 通过 Response 对象实现跨域;
- 通过实现 ResponseBodyAdvice 实现跨域。
当然如果你愿意的话,还可以使用过滤器来实现跨域,但它的实现和第 5 种实现类似,所以本文就不赘述了。
3.1 通过注解跨域
使用 @CrossOrigin 注解可以轻松的实现跨域,此注解既可以修饰类,也可以修饰方法。当修饰类时,表示此类中的所有接口都可以跨域;当修饰方法时,表示此方法可以跨域,它的实现如下:
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
@RestController
@CrossOrigin(origins = "*")
public class TestController {
@RequestMapping("/test")
public HashMap<String, Object> test() {
return new HashMap<String, Object>() {{
put("state", 200);
put("data", "success");
put("msg", "");
}};
}
}
以上代码的执行结果如下图所示:
从上图中可以看出,前端项目访问另一个后端项目成功了,也就说明它解决了跨域问题。优缺点分析此方式虽然虽然实现(跨域)比较简单,但细心的朋友也能发现,使用此方式只能实现局部跨域,当一个项目中存在多个类的话,使用此方式就会比较麻烦(需要给所有类上都添加此注解)。
3.2 通过配置文件跨域
接下来我们通过设置配置文件的方式就可以实现全局跨域了,它的实现步骤如下:
- 创建一个新配置文件;
- 添加 @Configuration 注解,实现 WebMvcConfigurer 接口;
- 重写 addCorsMappings 方法,设置允许跨域的代码。
具体实现代码如下:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration // 一定不要忽略此注解
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
// 设置跨域
response.getHeaders().set("Access-Control-Allow-Origin", "*");
return body;
}
}
此实现方式也是全局跨域,它对整个项目中的所有接口有效。
4.原理分析
为什么通过以上方法设置之后,就可以实现不同项目之间的正常交互呢?这个问题的答案也很简单,我们之前在说跨域时讲到:“跨域问题本质是浏览器的行为,它的初衷是为了保证用户的访问安全,防止恶意网站窃取数据”,那想要解决跨域问题就变得很简单了,只需要告诉浏览器这是一个安全的请求,“我是自己人”就行了,那怎么告诉浏览器这是一个正常的请求呢?只需要在返回头中设置“Access-Control-Allow-Origin”参数即可解决跨域问题,此参数就是用来表示允许跨域访问的原始域名的,当设置为“*”时,表示允许所有站点跨域访问,如下图所示:
所以以上 5 种解决跨域问题的本质都是给响应头中加了一个 Access-Control-Allow-Origin 的响应头而已。
演示项目源码
https://gitee.com/mydb/springboot-examples/tree/master/spring-boot-cross
总结
跨域问题的本质是浏览器为了保证用户的一种安全拦截机制,想要解决跨域问题,只需要告诉浏览器“我是自己人,不要拦我”就行。它的常见实现方式有 5 种:通过注解实现局部跨域、通过配置文件实现全局跨域、通过 CorsFilter 对象实现全局跨域、通过 Response 对象实现局部跨域,通过 ResponseBodyAdvice 实现全局跨域。