背景:前端在抖音里做了一个插件然后访问我们的后端。显然在抖音访问其他域名肯定会跨域。
解决办法:
1、使用比较简单的jsonp
JSONP
优点:JSONP 是通过动态创建 <script>
标签的方式加载外部数据,属于跨域数据请求的一种传统解决方案。JSONP 不会受到浏览器的同源策略限制,因此在某些跨域环境(如抖音小程序中)可以正常工作。
缺点:只能进行 GET 请求,无法支持 POST、PUT 等其他 HTTP 方法。
安全性较低,因为 JSONP 将代码直接注入到页面中,存在潜在的安全风险。
测试案例:
后端 springboot
@RestController
@Slf4j
@RequestMapping("/api")
public class CrosController {
@GetMapping("/author/info")
public String crosTest(@RequestParam("authorName") String authorName,
@RequestParam(value = "callback", required = false) String callback){
// 参数校验
if (authorName == null) {
return "Invalid request: authorName is required";
}
// 模拟返回数据
String jsonData = "{\"author_id\": 12345, \"author_name\": \"" + authorName + "\"}";
// 判断是否为 JSONP 请求
if (callback != null && !callback.isEmpty()) {
// JSONP 响应
String jsonpResponse = callback + "(" + jsonData + ");";
return jsonpResponse;
} else {
// 普通 JSON 响应
return jsonData;
}
}
}
前端html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
<script>
function jsonpRequest(url, params, callbackName) {
return new Promise((resolve, reject) => {
// 动态创建回调函数名称
const uniqueCallback = callbackName || `jsonpCallback_${Date.now()}`;
// 将回调函数注册到全局对象中
window[uniqueCallback] = function (response) {
// 清理全局回调函数和 script 标签
delete window[uniqueCallback];
document.body.removeChild(script);
// 返回响应数据
resolve(response);
};
// 构建完整的 URL(拼接参数)
const query = Object.entries(params)
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
.join("&");
const fullUrl = `${url}?${query}&callback=${uniqueCallback}`;
// 创建 script 标签
const script = document.createElement("script");
script.src = fullUrl;
// 处理加载失败
script.onerror = function () {
delete window[uniqueCallback];
document.body.removeChild(script);
reject(new Error("JSONP request failed"));
};
// 插入 script 标签到文档中
document.body.appendChild(script);
});
}
// 使用示例
const url = "http://localhost:8080/api/author/info";
const params = { authorName: "烟火中的水滴" };
jsonpRequest(url, params)
.then((data) => {
console.log("Author Info:", data);
})
.catch((error) => {
console.error("Error:", error);
});
</script>
然后打开次页面。
1、打开控制台 2、执行 可以看到结果正常返回了
jsonpRequest(url, params)
.then((data) => {
console.log("Author Info:", data);
})
.catch((error) => {
console.error("Error:", error);
});
2、@CrossOrigin 解决跨域
Spring Boot 的 @CrossOrigin 注解用于配置 CORS(跨域资源共享),它允许你的后端接受来自其他域的请求。
优点:CORS 是更现代、更安全的跨域解决方案,相比 JSONP,支持所有 HTTP 方法(如 GET、POST 等)。安全性高,因为 CORS 会进行严格的域名、方法等验证。
缺点:如果客户端(如抖音小程序)对 CORS 的处理有限,可能无法支持复杂的预检请求(如 OPTIONS 请求)。CORS 依赖浏览器的支持,如果环境对跨域限制较为严格(如一些小程序环境),可能 CORS 不生效。
我们先开启后端运行起来。看一些问题
1、通过控制台源null模拟跨域
后端代码
@RestController
@Slf4j
@RequestMapping("/api")
public class CrosController {
@GetMapping("/author/info/cross")
public String crossTest(@RequestParam("authorName") String authorName){
// 参数校验
if (authorName == null) {
return "Invalid request: authorName is required";
}
// 模拟返回数据
String jsonData = "{\"author_id\": 12345, \"author_name\": \"" + authorName + "\"}";
return jsonData;
}
}
1、通过curl
请求
curl 'http://localhost:8080/api/author/info/cross?authorName=123'
结果
{"author_id": 12345, "author_name": "123"}
这个属于正常的
2、打开一个index.html 然后控制台通过 前端代码访问这个接口
fetch('http://localhost:8080/api/author/info?authorName=烟火中的水滴')
.then(response => response.json())
.then(data => console.log("JSON Response:", data))
.catch(error => console.error("Error:", error));
发现报错
问题分析
错误提示:
No ‘Access-Control-Allow-Origin’ header is present on the requested resource 表示目标服务器(http://localhost:8080)没有返回允许跨域访问的CORS响应头。
浏览器的安全机制会拦截请求响应。
根本原因:
客户端代码运行的index.html被浏览器认为是从null源加载的(例如直接从文件系统打开),与http://localhost:8080是不同源。
目标服务器未配置CORS。
其实这个就是跨域了因为你从一个控制台打开访问8080端口 但是你不是同源的就会报错。
那么解决办法就是加一个注解
@CrossOrigin(origins = "*") // 允许指定来源跨域
@GetMapping("/author/info/cross")
public String crossTest(@RequestParam("authorName") String authorName){
// 参数校验
if (authorName == null) {
return "Invalid request: authorName is required";
}
// 模拟返回数据
String jsonData = "{\"author_id\": 12345, \"author_name\": \"" + authorName + "\"}";
return jsonData;
}
重新访问
fetch('http://localhost:8080/api/author/info/cross?authorName=烟火中的水滴') .then(response => response.json()) .then(data => console.log("JSON Response:", data)) .catch(error => console.error("Error:", error))
成功了
我们看下他发起的curl请求参数:
curl 'http://localhost:8080/api/author/info/cross?authorName=%E7%83%9F%E7%81%AB%E4%B8%AD%E7%9A%84%E6%B0%B4%E6%BB%B4' \
-H 'Accept: */*' \
-H 'Accept-Language: zh-CN,zh;q=0.9' \
-H 'Connection: keep-alive' \
-H 'Origin: null' \
-H 'Sec-Fetch-Dest: empty' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Site: cross-site' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36' \
-H 'sec-ch-ua: "Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "macOS"'
源是null实际也属于跨域的。
上面是的源是null 我们接下来用python模拟一个源请求
模拟跨域,就得开启一个前端服务器 然后请求后端。
2、模拟前端服务器 端口3000实现跨域
使用 Python(自带)启动 HTTP 服务器: 在终端中进入你的 index.html 所在的目录,并运行以下命令:
python -m http.server 3000
这会在 localhost:3000 上启动一个 HTTP 服务器。然后在浏览器中访问:
然后打开浏览器 访问 http://localhost:3000/index.html
打开控制台执行
fetch('http://localhost:8080/api/author/info/cross?authorName=烟火中的水滴') .then(response => response.json()) .then(data => console.log("JSON Response:", data)) .catch(error => console.error("Error:", error))
最后我们打开Network看下他发起的请求是不是真的跨域了。
通过查看curl命令确实跨域了
curl 'http://localhost:8080/api/author/info/cross?authorName=%E7%83%9F%E7%81%AB%E4%B8%AD%E7%9A%84%E6%B0%B4%E6%BB%B4' \
-H 'Accept: */*' \
-H 'Accept-Language: zh-CN,zh;q=0.9' \
-H 'Connection: keep-alive' \
-H 'Origin: http://localhost:3000' \
-H 'Referer: http://localhost:3000/' \
-H 'Sec-Fetch-Dest: empty' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Site: same-site' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36' \
-H 'sec-ch-ua: "Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "macOS"'