Gin框架API隔次请求无法接收Authorization Header问题排查
问题描述
用Go Gin重构API后,iOS客户端发起请求出现异常:首次请求携带Authorization Header可正常通过鉴权,但第二次切换不同端点请求时,该Header丢失导致被拦截。此前用TypeScript实现时无此问题,仅Gin重构后出现。
复现步骤
- 从iOS客户端调用
/estimate端点,请求成功 - 再次调用另一个POST端点(如
/),请求因缺少Authorization Header失败
服务端核心代码
package main import ( "github.com/gin-gonic/gin" ) // 省略其他导入与无关代码... func Routes(r *gin.Engine, db *mongo.Database, g *gateman.Gateman, conn *amqp091.Connection) { atHandler := pc.NewPackagesController(ps.NewPackagesService(pr.NewPackagesRepository(db)), g, events.NewEventEmitter(conn)) r.Use(CORS()) v1 := r.Group("/api/v1/package") { v1.POST("/query", GraphqlHandler(db, directives.NewDirectivesManager(g))) v1.GET("/", PlaygroundHandler(db)) v1.POST("/", g.Guard([]string{"user"}, nil), atHandler.Create) v1.POST("/estimate", g.Guard([]string{"user"}, nil), atHandler.Estimate) // 省略其他路由定义 v1.GET("/healthz", atHandler.GetHealth) } r.GET("/", atHandler.GetUP) } func CORS() gin.HandlerFunc { return func(c *gin.Context) { c.Writer.Header().Set("Access-Control-Allow-Origin", "*") c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } c.Next() } } // 省略其他代码...
客户端核心代码
func request<T>(with builder: BaseRequest, customDecoder: JSONDecoder) -> AnyPublisher<T, APIError> where T: Codable { let encoding: ParametersEncoder = [.get, .delete].contains(builder.method) ? URLParameretersEncoder() : JSONParametersEncoder() customDecoder.keyDecodingStrategy = .convertFromSnakeCase var url: URL { var components = URLComponents() components.scheme = "https" components.host = builder.baseUrl components.path = "/api/v1" + builder.path guard let url = components.url else { preconditionFailure("Invalid URL components: \(components)") } return url } var urlRequest = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 46.0) urlRequest.httpMethod = builder.method.rawValue builder.headers.forEach { key, value in urlRequest.setValue(value, forHTTPHeaderField: key) } if let token = tokenManager.token { urlRequest.setValue("Bearer " + token, forHTTPHeaderField: "Authorization") } if let parameters = builder.parameters { guard let encoded = try? encoding.encode(parameters: parameters, in: urlRequest) else { fatalError() } urlRequest = encoded } self.log(request: urlRequest) return URLSession.shared .dataTaskPublisher(for: urlRequest) .receive(on: DispatchQueue.main) .mapError {_ -> APIError in UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) return .unknown } .flatMap { data, response -> AnyPublisher<T, APIError> in // 省略响应处理代码 } .eraseToAnyPublisher() } protocol BaseRequest { var baseUrl: String { get } var path: String { get } var headers: HTTPHeaders { get } var method: HTTPRequestMethod { get } var parameters: HTTPParameters { get } } public typealias HTTPHeaders = [String: String] public typealias HTTPParameters = [String: Any]?
预期与实际结果
- 预期:所有受鉴权保护的端点,只要请求携带有效Authorization Header就应通过
- 实际:首次请求任意POST端点成功;切换不同POST端点时,第二次请求丢失Authorization Header导致失败。Postman测试无此问题,仅iOS客户端出现,多次调用同一端点无异常。
环境信息
- Go版本:1.19
- Gin版本:v1.8.1
- 操作系统:Mac开发机、iOS移动端
问题根源与解决方案
1. CORS配置冲突(核心问题)
当前CORS设置中,Access-Control-Allow-Origin: *和Access-Control-Allow-Credentials: true违反HTTP规范:允许携带凭证时,Access-Control-Allow-Origin不能设为通配符*,必须指定具体源。iOS的URLSession会因该不合法配置,在后续跨域请求中自动移除Authorization Header。
修复代码:
修改CORS中间件,动态获取请求Origin并设置(可添加白名单校验确保安全):
func CORS() gin.HandlerFunc { return func(c *gin.Context) { // 动态获取请求Origin origin := c.Request.Header.Get("Origin") // 可选:添加白名单校验,仅允许信任的源 // allowedOrigins := []string{"https://your-app-domain.com"} // for _, allowed := range allowedOrigins { // if allowed == origin { // c.Writer.Header().Set("Access-Control-Allow-Origin", origin) // break // } // } c.Writer.Header().Set("Access-Control-Allow-Origin", origin) c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } c.Next() } }
2. 检查客户端编码逻辑
确认JSONParametersEncoder实现是否会覆盖原有Header。若编码函数内部重新创建URLRequest但未复制原Header,会导致Authorization丢失。
验证建议:
在编码后添加日志,确认Header是否存在:
if let parameters = builder.parameters { guard let encoded = try? encoding.encode(parameters: parameters, in: urlRequest) else { fatalError() } urlRequest = encoded // 打印编码后的Header print("Encoded request headers: \(urlRequest.allHTTPHeaderFields ?? [:])") }
若Header丢失,修改编码器逻辑,确保复制原请求所有Header后再添加参数。
3. 确认中间件顺序
当前代码中r.Use(CORS())在路由组定义前,确保了CORS中间件优先执行,无需调整。若后续新增中间件,需保证CORS始终是第一个被应用的中间件,避免OPTIONS请求被提前拦截。
内容的提问来源于stack exchange,提问作者King
相关产品推荐
相关产品推荐

