0

0

深度解析CORS预检请求:解决自定义Header导致的OPTIONS请求失败

心靈之曲

心靈之曲

发布时间:2025-11-30 15:22:01

|

564人浏览过

|

来源于php中文网

原创

深度解析CORS预检请求:解决自定义Header导致的OPTIONS请求失败

当web应用发起包含自定义http头的跨域请求时,浏览器会首先发送一个“预检(preflight)”options请求。本文将深入探讨这一机制,并通过go语言服务器端的具体案例,演示如何正确处理这些预检请求,以确保带有自定义`authorization`等头的cors get请求能够顺利执行,避免常见的404或cors错误。

理解CORS与预检请求

跨域资源共享(CORS)是一种W3C标准,它允许浏览器向跨源服务器发出XMLHttpRequest请求,从而克服了同源策略的限制。然而,并非所有跨域请求都会直接发送。对于某些“非简单请求”,浏览器会先自动发送一个HTTP OPTIONS请求到目标资源,以确定实际请求是否安全可发送。这个OPTIONS请求被称为“预检请求”(Preflight Request)。

非简单请求的条件通常包括:

  • 使用了GET、HEAD、POST以外的HTTP方法(如PUT、DELETE)。
  • 发送了浏览器自动设置以外的HTTP头,例如Authorization、X-Custom-Header等自定义头部。
  • Content-Type的值不是application/x-www-form-urlencoded、multipart/form-data或text/plain。

当一个AngularJS应用(或其他前端框架)发起一个带有Authorization自定义头的GET请求时,即使GET本身是简单请求方法,但由于Authorization头的存在,该请求会被视为非简单请求,从而触发浏览器发送预检OPTIONS请求。

问题现象:预检OPTIONS请求失败

考虑以下前端AngularJS代码,它尝试向/banks接口发送一个带有Authorization头的GET请求:

$http.get(env.apiURL()+'/banks', {
    headers: {
        'Authorization': 'Bearer '+localStorageService.get('access_token')
    }
})

当这个请求被触发时,浏览器实际会先发送一个OPTIONS请求,其结构大致如下:

OPTIONS /banks HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Access-Control-Request-Method: GET
Origin: http://localhost:8081
Access-Control-Request-Headers: accept, authorization // 注意这里包含了 'authorization'
Accept: */*
// ... 其他头部

如果服务器端没有明确处理这个OPTIONS请求,或者路由配置中没有对应的OPTIONS处理器,服务器可能会返回一个404 Not Found错误,因为默认情况下它只知道如何处理GET请求。例如,一个Go语言服务器可能只配置了GET路由:

r := mux.NewRouter()
r.HandleFunc("/banks", RetrieveAllBank).Methods("GET")
http.ListenAndServe(":8080", r)

在这种情况下,OPTIONS /banks请求将不会匹配到任何路由,导致服务器返回404 Not Found。即使服务器在后续的GET请求中设置了正确的CORS响应头(如Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers),由于预检请求的失败,浏览器会阻止实际的GET请求发出,并在控制台报告CORS错误。

Artbreeder
Artbreeder

创建令人惊叹的插画和艺术

下载

服务器返回的404响应可能看起来像这样,即使其中包含了CORS相关的响应头,也无济于事,因为请求方法不匹配:

HTTP/1.1 404 Not Found
Access-Control-Allow-Headers: Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization
Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE
Access-Control-Allow-Origin: http://localhost:8081
Content-Type: text/plain; charset=utf-8
Date: Mon, 17 Mar 2014 11:05:20 GMT
Content-Length: 19

解决方案:服务器端处理OPTIONS预检请求

解决此问题的关键在于服务器端必须明确地处理OPTIONS请求。当服务器收到OPTIONS请求时,它应该检查请求头中的Access-Control-Request-Method和Access-Control-Request-Headers,并用适当的CORS响应头进行回复,告知浏览器允许哪些方法和头部。

以下是Go语言服务器的修改示例,通过实现http.Handler接口来拦截并处理OPTIONS请求:

package main

import (
    "fmt"
    "github.com/gorilla/mux"
    "log"
    "net/http"
)

// RetrieveAllBank 模拟实际的GET请求处理器
func RetrieveAllBank(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Welcome to the banks API!")
}

// MyServer 结构体,用于包装mux.Router并实现http.Handler接口
type MyServer struct {
    r *mux.Router
}

// ServeHTTP 方法实现http.Handler接口
func (s *MyServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
    // 设置CORS响应头,允许指定来源、方法和头部
    if origin := req.Header.Get("Origin"); origin == "http://localhost:8081" {
        rw.Header().Set("Access-Control-Allow-Origin", origin)
        rw.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
        rw.Header().Set("Access-Control-Allow-Headers",
            "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") // 确保包含所有自定义头
        rw.Header().Set("Access-Control-Max-Age", "86400") // 预检请求的缓存时间,单位秒
    }

    // 如果是预检OPTIONS请求,则直接返回,不继续处理路由
    if req.Method == "OPTIONS" {
        rw.WriteHeader(http.StatusOK) // 返回200 OK
        return
    }

    // 如果不是OPTIONS请求,则交给Gorilla Mux路由器处理
    s.r.ServeHTTP(rw, req)
}

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/banks", RetrieveAllBank).Methods("GET") // 注册GET方法路由

    fmt.Println("Server listening on :8080")
    // 将自定义的MyServer作为http.ListenAndServe的处理器
    log.Fatal(http.ListenAndServe(":8080", &MyServer{r}))
}

代码解析:

  1. MyServer结构体和ServeHTTP方法: 我们创建了一个MyServer结构体来包装*mux.Router,并为其实现了http.Handler接口的ServeHTTP方法。这样,MyServer就可以作为整个HTTP服务器的入口处理器。
  2. 设置CORS响应头: 在ServeHTTP方法的开头,我们统一设置了CORS相关的响应头。
    • Access-Control-Allow-Origin: 指定允许哪些源(Origin)访问资源。
    • Access-Control-Allow-Methods: 指定允许哪些HTTP方法(GET, POST, OPTIONS等)访问资源。务必包含OPTIONS方法。
    • Access-Control-Allow-Headers: 这是关键! 它指定了实际请求中允许携带哪些自定义HTTP头。如果客户端发送了Authorization头,这里就必须包含Authorization。
    • Access-Control-Max-Age: 可选,用于指定预检请求的缓存时间,避免每次请求都发送预检。
  3. 处理OPTIONS请求:
    if req.Method == "OPTIONS" {
        rw.WriteHeader(http.StatusOK) // 返回200 OK
        return
    }

    当请求方法是OPTIONS时,我们设置了必要的CORS头后,直接返回http.StatusOK(200 OK),并且不再将请求传递给底层的mux.Router处理。这告诉浏览器,预检成功,实际请求可以安全发送。

  4. 处理非OPTIONS请求: 如果请求方法不是OPTIONS,则调用s.r.ServeHTTP(rw, req),将请求交给原始的mux.Router进行处理,例如匹配GET /banks路由。

注意事项与最佳实践

  • 自定义Header的大小写: 浏览器在发送Access-Control-Request-Headers时,通常会将自定义头名转换为小写(例如Authorization变为authorization)。但是,在服务器端设置Access-Control-Allow-Headers时,推荐使用原始的、大小写敏感的名称(如Authorization),因为有些客户端或服务器实现可能对此敏感,且规范通常使用原始大小写。Go语言的http.Header处理通常是大小写不敏感的,但为了兼容性,保持一致性是好的做法。在本例中,前端发送Authorization,后端Access-Control-Allow-Headers也应包含Authorization。
  • *通配符`的使用:**Access-Control-Allow-Origin可以设置为来允许所有来源,但这在生产环境中通常不推荐,因为它会降低安全性。Access-Control-Allow-Headers和Access-Control-Allow-Methods也可以使用`,但同样应谨慎使用,仅在明确了解风险的情况下。
  • CORS中间件: 在更复杂的Go应用中,为了避免在每个Handler中重复CORS逻辑,可以考虑使用现有的CORS中间件库,例如github.com/rs/cors,它们提供了更灵活和健壮的CORS配置选项。
  • 错误处理: 确保在处理OPTIONS请求时,即使没有匹配到特定路由,也能返回200 OK以及正确的CORS头,而不是404 Not Found。

通过以上修改,前端AngularJS应用发出的带有自定义Authorization头的GET请求,在经过预检OPTIONS请求成功后,将能够顺利到达服务器并获取数据。理解并正确处理CORS预检请求是构建健壮跨域Web应用的关键一步。

相关专题

更多
什么是中间件
什么是中间件

中间件是一种软件组件,充当不兼容组件之间的桥梁,提供额外服务,例如集成异构系统、提供常用服务、提高应用程序性能,以及简化应用程序开发。想了解更多中间件的相关内容,可以阅读本专题下面的文章。

177

2024.05.11

Golang 中间件开发与微服务架构
Golang 中间件开发与微服务架构

本专题系统讲解 Golang 在微服务架构中的中间件开发,包括日志处理、限流与熔断、认证与授权、服务监控、API 网关设计等常见中间件功能的实现。通过实战项目,帮助开发者理解如何使用 Go 编写高效、可扩展的中间件组件,并在微服务环境中进行灵活部署与管理。

212

2025.12.18

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

195

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

187

2025.07.04

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1017

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

62

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

400

2025.12.29

Go中Type关键字的用法
Go中Type关键字的用法

Go中Type关键字的用法有定义新的类型别名或者创建新的结构体类型。本专题为大家提供Go相关的文章、下载、课程内容,供大家免费下载体验。

233

2023.09.06

Java 桌面应用开发(JavaFX 实战)
Java 桌面应用开发(JavaFX 实战)

本专题系统讲解 Java 在桌面应用开发领域的实战应用,重点围绕 JavaFX 框架,涵盖界面布局、控件使用、事件处理、FXML、样式美化(CSS)、多线程与UI响应优化,以及桌面应用的打包与发布。通过完整示例项目,帮助学习者掌握 使用 Java 构建现代化、跨平台桌面应用程序的核心能力。

36

2026.01.14

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WEB前端教程【HTML5+CSS3+JS】
WEB前端教程【HTML5+CSS3+JS】

共101课时 | 8.3万人学习

JS进阶与BootStrap学习
JS进阶与BootStrap学习

共39课时 | 3.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号