CORS 预检请求的基本原理
CORS即跨域资源共享,是浏览器为了安全限制跨域请求而推出的标准机制。当浏览器发起的非简单请求满足特定条件时,会先发送一个OPTIONS方法的预检请求,询问服务器是否允许当前的跨域请求。只有预检请求通过,浏览器才会发送真正的业务请求。

预检请求会携带Origin、Access-Control-Request-Method、Access-Control-Request-Headers等请求头,服务器需要正确响应这些请求头,返回对应的允许配置,才能让后续的跨域请求正常执行。
手动处理预检请求的实现方式
如果不想引入第三方依赖,可以手动在Go服务器中处理OPTIONS预检请求,核心逻辑是判断请求方法是否为OPTIONS,然后设置对应的响应头。
基础处理示例
以下是一个简单的手动处理CORS预检请求的代码示例:
package main
import (
"net/http"
)
// corsMiddleware 是处理CORS的中间件
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 设置允许的源,生产环境建议指定具体域名而非*
w.Header().Set("Access-Control-Allow-Origin", "*")
// 设置允许的请求方法
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
// 设置允许的请求头
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
// 设置是否允许携带凭证
w.Header().Set("Access-Control-Allow-Credentials", "false")
// 预检请求的缓存时间,单位秒
w.Header().Set("Access-Control-Max-Age", "86400")
// 如果是OPTIONS预检请求,直接返回200状态码
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
// 执行下一个处理器
next.ServeHTTP(w, r)
})
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello cors"))
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/hello", helloHandler)
// 使用CORS中间件包装路由
wrappedMux := corsMiddleware(mux)
http.ListenAndServe(":8080", wrappedMux)
}
手动处理的注意事项
- 生产环境中
Access-Control-Allow-Origin不要直接设置为*,应该根据请求的Origin头动态判断,只允许可信的域名跨域访问。 - 如果接口需要携带Cookie等凭证,
Access-Control-Allow-Credentials要设置为true,同时Access-Control-Allow-Origin不能为*,必须指定具体域名。 Access-Control-Max-Age设置合理的值可以减少预检请求的发送频率,提升请求效率。
使用第三方库处理的最佳实践
手动处理需要自己处理很多边界情况,使用成熟的第三方库可以更高效且避免遗漏配置。常用的Go CORS处理库是github.com/rs/cors,它提供了丰富的配置选项。
库的使用示例
首先安装依赖:
go get github.com/rs/cors
然后使用库处理CORS预检请求的代码示例如下:
package main
import (
"net/http"
"github.com/rs/cors"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello cors with library"))
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/hello", helloHandler)
// 配置CORS选项
c := cors.New(cors.Options{
// 允许的源列表,支持多个域名
AllowedOrigins: []string{"https://ipipp.com", "http://localhost:3000"},
// 允许的请求方法
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
// 允许的请求头
AllowedHeaders: []string{"Content-Type", "Authorization"},
// 暴露给客户端的响应头
ExposedHeaders: []string{"X-Request-Id"},
// 是否允许携带凭证
AllowCredentials: true,
// 预检请求缓存时间
MaxAge: 86400,
})
// 将CORS处理器包装路由
handler := c.Handler(mux)
http.ListenAndServe(":8080", handler)
}
第三方库的优势
- 配置项完善,支持动态源判断、正则匹配源等高级功能,不需要自己实现复杂的逻辑。
- 经过了大量项目的验证,边界情况处理更完善,比如处理带自定义头的预检请求、处理预检请求的缓存逻辑等。
- 维护活跃,遇到问题可以快速找到解决方案或者得到更新支持。
不同场景下的配置建议
| 场景 | 配置建议 |
|---|---|
| 开发环境前后端联调 | 可以暂时将AllowedOrigins设置为*,方便本地调试,不需要频繁修改配置。 |
| 生产环境公开接口 | 指定具体的允许跨域的域名,关闭凭证允许,设置合理的预检缓存时间。 |
| 生产环境需要携带Cookie的接口 | 指定具体的允许源,开启AllowCredentials,同时前端请求时需要设置withCredentials为true。 |
| 需要支持自定义请求头的接口 | 将自定义头名称添加到AllowedHeaders中,否则预检请求会失败。 |
常见错误排查
- 预检请求返回404:检查是否正确拦截了OPTIONS方法的请求,中间件是否被正确注册到路由上。
- 响应头缺失:检查是否在预检请求的处理逻辑中遗漏了对应的
Access-Control-*响应头的设置。 - 携带凭证时跨域失败:检查
Access-Control-Allow-Credentials是否为true,同时Access-Control-Allow-Origin是否为具体域名而非*。