背景:前端在抖音里做了一个插件然后访问我们的后端。显然在抖音访问其他域名肯定会跨域。
解决办法:
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"'