讲师中心 微信公众号

工具推荐

视频效率加速
updream

一键生成高质量推广成片

立刻MV
立刻MV

低成本歌曲 MV、视频生成

Laper
Laper

剧本策划到成片协作

剧云
剧云

中文短剧剧本制作平台

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

秋辰同学_3301

秋辰同学_3301

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

|

644人浏览过

|

来源于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错误。

Investment Researcher Digital Employee
Investment Researcher Digital Employee

覆盖宏观策略、固收研究、行业公司深度分析、量化因子、大类资产配置、可转债、市场情绪、政策快评、行业景气度追踪等34项核心研究能力,证券研究员的全能数字助手。

下载

服务器返回的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应用的关键一步。

热门AI工具

更多
Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

Lovart
Lovart Hot

一款面向视觉设计创作的AI设计平台,可通过智能体和画布工作流辅助制作海报、Logo、网页、PPT及其他视觉内容。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

小橡皮AI

一款AI工具,主要用于小橡皮一键去AI味,将机器味重的内容改得更有活人感,防止因为 AI 内容被平台打上 AI 标签,以及限流,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

Laper
Laper Hot

Laper是专为编剧、导演和制片人推出的 AI 原生剧本创作工具。

相关专题

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

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

729

2024.05.11

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

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

664

2025.12.18

ThinkPHP中间件机制与请求拦截处理实践
ThinkPHP中间件机制与请求拦截处理实践

本专题围绕 ThinkPHP 中间件体系展开,深入讲解中间件的定义、注册与执行流程。内容包括全局中间件与路由中间件的区别、请求前后处理逻辑、自定义中间件开发以及权限验证与日志处理应用。通过实际案例,帮助开发者掌握中间件在项目中的核心作用与最佳实践。

458

2026.03.31

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

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

5414

2025.06.09

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

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

5591

2025.07.04

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

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

3248

2023.10.19

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

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

5449

2025.10.17

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

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

3909

2025.12.29

Go-Zero API开发使用教程
Go-Zero API开发使用教程

本专题聚焦Go-Zero的API开发,拆解.api描述文件的语法结构、路由与分组定义、参数绑定与校验规则,说明统一响应体、JWT鉴权、跨域与接口文档生成的落地方式,并给出网关聚合与接口测试的实践建议。

20

2026.11.12

热门下载

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

精品课程

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

共101课时 | 21.3万人学习

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

共39课时 | 4.9万人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn