一站式了解跨域问题

引言

在平时的后台开发中,我们会经常遇到跨域问题,那么跨域问题到底是什么,怎么去解决跨域问题呢?今天我们就一起探讨一下。

同源策略

跨域问题本质上是由于浏览器的同源策略(Same-Origin Policy)引起的。

同源策略是浏览器的一个安全限制。它规定,一个源(Origin)的文档或脚本只能与同源的资源进行交互。

  • "源" 由三个部分组成:协议(Protocol)主机(Host/Domain)端口号(Port)
  • 如果请求的 URL 与当前页面的 URL 相比,任一 部分不同,就被认为是跨源
当前页面 URL 目标 URL 是否同源? 跨源原因
http://a.com:8080/index.html http://a.com:8080/data.json (全部相同)
http://a.com:8080/ **https**://a.com:8080/ 协议不同 (http vs https)
http://a.com:8080/ http://**b.com**:8080/ 主机不同 (a.com vs b.com)
http://a.com:8080/ http://a.com:**9090**/** 端口不同 (8080 vs 9090)

同源策略的目的是保护用户安全和隐私,防止恶意网站通过浏览器脚本访问其他网站的敏感数据。

跨域问题如何产生?

当浏览器中的前端代码(如使用 XMLHttpRequestFetch API)尝试向一个不同源的服务器发起 HTTP 请求时,浏览器会拦截服务器返回的数据,导致请求失败(但在网络层面上,请求实际上已经发送到服务器并收到了响应)。

这就是我们在开发中常说的跨域报错 ,通常你会看到类似 No 'Access-Control-Allow-Origin' header is present on the requested resource 的错误信息。

解决方案一:CORS(跨源资源共享)

在本地开发测试阶段可以通过配置cors来解决跨域问题。

跨源资源共享(Cross-Origin Resource Sharing, CORS )是一种允许浏览器放宽同源策略限制的机制。它通过在 服务器端 设置特定的 HTTP 响应头,来告知浏览器该服务器允许哪些源访问其资源。

CORS 主要分为两种请求模式:

1. 简单请求(Simple Requests)

如果请求同时满足以下所有条件,则被认为是简单请求:

  • 方法: 只能是 GETPOSTHEAD 之一。

  • 请求头: 只能包含少数几个安全的请求头(如 AcceptAccept-LanguageContent-LanguageContent-Type 等),且 Content-Type 只能是以下三种之一:

    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

工作流程:

  1. 浏览器直接发送请求,并在请求头中带上 Origin 字段,表明自己的源。
  2. 服务器收到请求后,如果允许跨源访问,则在响应头中加入 Access-Control-Allow-Origin 字段,指定允许的源(例如:Access-Control-Allow-Origin: http://client.comAccess-Control-Allow-Origin: *)。
  3. 浏览器检查响应头,如果发现 Access-Control-Allow-Origin 允许当前源,则将数据交给前端代码;否则,抛出跨域错误。

2. 预检请求(Preflighted Requests)

对于非简单请求 (例如:使用了 PUTDELETE 方法,或者设置了自定义请求头),浏览器会先发送一个使用 OPTIONS 方法的预检请求,以确定服务器是否安全。

工作流程:

  1. 浏览器发送 OPTIONS 预检请求: 请求头包含:

    • Origin:当前源。
    • Access-Control-Request-Method:实际请求将使用的方法(如 POST)。
    • Access-Control-Request-Headers:实际请求将携带的自定义请求头(如 X-Custom-Header)。
  2. 服务器处理预检请求: 服务器检查这些请求头,如果允许,则在预检请求的响应头中返回:

    • Access-Control-Allow-Origin:允许的源。
    • Access-Control-Allow-Methods:允许的方法。
    • Access-Control-Allow-Headers:允许的自定义请求头。
    • Access-Control-Max-Age:预检结果的缓存时间(秒)。
  3. 浏览器检查预检结果: 如果预检通过,浏览器才会发送实际的 HTTP 请求(GET/POST/PUT/DELETE 等)。

  4. 服务器处理实际请求 并返回数据(响应头中通常仍会包含 Access-Control-Allow-Origin)。

代码示例

Java (Spring Boot 示例)

在 Spring Boot 中,你可以通过配置 WebMvcConfigurer 或在 Controller 上使用 @CrossOrigin 注解来解决:

java 复制代码
// 方法一:全局配置 (推荐)
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") // 匹配所有路径
                .allowedOrigins("http://your-frontend-domain.com") // 允许的源,可以设为 "*" 允许所有(不推荐用于生产)
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的方法
                .allowedHeaders("*") // 允许所有请求头
                .allowCredentials(true) // 是否允许携带 Cookie
                .maxAge(3600); // 预检请求的缓存时间
    }
}

Go (Gin 框架示例)

如果你使用 Gin 框架,可以借助第三方 CORS 中间件,例如 github.com/gin-contrib/cors

go 复制代码
// Go 示例 (使用 Gin 框架和 gin-contrib/cors 中间件)
package main

import (
	"time"
	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()

	// 配置 CORS 中间件
	r.Use(cors.New(cors.Config{
		AllowOrigins:     []string{"http://your-frontend-domain.com"}, // 允许的源
		AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
		AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"}, // 允许的请求头
		ExposeHeaders:    []string{"Content-Length"}, // 允许前端访问的响应头
		AllowCredentials: true, // 允许携带 Cookie
		MaxAge:           12 * time.Hour,
	}))

	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})

	r.Run()
}

解决方案二:反向代理

这是生产环境中最常用且推荐的方案。前端请求同源的代理服务器,由代理服务器转发请求给目标服务器。因为请求发生在服务器之间,不受浏览器同源策略限制。

当浏览器向代理服务器(例如 http://api.frontend.com)发送请求时,由于前端页面和代理服务器是同源 的,浏览器不会触发同源策略限制。代理服务器在收到请求后,再将其转发到后端服务(例如 http://backend-service:8080)。服务器之间的通信不存在跨域限制

集中管理与解耦

将跨域配置、SSL/TLS 证书、负载均衡、限流、缓存等所有非业务性的公共配置,全部集中在反向代理层处理。

  • 后端服务解耦: 后端服务(Java/Go)可以专注于业务逻辑,无需关心这些基础设施配置。
  • 配置统一: 只需要在 Nginx 或 Gateway 上配置一次,适用于所有后端服务

安全性与架构优势

  • 隐藏真实服务地址: 客户端只能看到代理服务器的地址,后端服务的真实 IP 和端口被隐藏,提高了安全性。

  • 更细致的访问控制: 代理层可以更容易地实现基于路径的访问控制和限流

架构示意图

总结

解决跨域问题,在开发测试环境中,为了简单快捷我们可以使用cors。在生产部署环境中,强烈建议使用反向代理。

相关推荐
古城小栈2 小时前
雾计算架构:边缘-云端协同的分布式 AI 推理
人工智能·分布式·架构
Allen正心正念20252 小时前
AWS专家Greg Coquillo提出的 6种LLM ORCHESTRATION PATTERNS解析
人工智能·架构
伊玛目的门徒2 小时前
HTTP SSE 流式响应处理:调用腾讯 智能应用开发平台ADP智能体的 API
python·网络协议·http·腾讯智能体·adp·智能应用开发平台
2501_938810112 小时前
动态IP的使用方法
网络·网络协议·tcp/ip
Selegant2 小时前
告别传统部署:用 GraalVM Native Image 构建秒级启动的 Java 微服务
java·开发语言·微服务·云原生·架构
动亦定3 小时前
微服务中如何保证数据一致性?
java·数据库·微服务·架构
无限大.3 小时前
为什么网站需要“域名“?——从 IP 地址到网址的演进
网络·网络协议·tcp/ip
wha the fuck4043 小时前
(渗透脚本)TCP创建连接脚本----解题----极客大挑战2019HTTP
python·网络协议·tcp/ip·网络安全·脚本书写
彷徨的蜗牛3 小时前
六边形架构补充 - 第五章 - DDD领域模型
架构·领域模型·ddd