首页 > 后端开发 > Golang > 正文

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

心靈之曲
发布: 2025-11-30 15:22:01
原创
527人浏览过

深度解析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错误。

猫眼课题宝
猫眼课题宝

5分钟定创新选题,3步生成高质量标书!

猫眼课题宝 262
查看详情 猫眼课题宝

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

以上就是深度解析CORS预检请求:解决自定义Header导致的OPTIONS请求失败的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

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