Golang实现带用户名密码认证的HTTP代理及BasicAuth报错排查
问题根因
r.BasicAuth()持续解析失败的核心原因是混淆了普通Web服务认证和HTTP代理认证的协议规范:
- 普通Web服务的Basic Auth读取
Authorization请求头,认证失败返回401 Unauthorized,搭配WWW-Authenticate响应头 - HTTP代理的Basic Auth读取*
Proxy-Authorization请求头,认证失败返回407 Proxy Authentication Required,搭配Proxy-Authenticate*响应头
Go标准库的r.BasicAuth()方法仅会解析Authorization头,不会处理代理专用的Proxy-Authorization头;同时原代码直接返回401状态码,客户端不会识别为代理认证要求,不会主动传递配置的账号密码,最终导致认证一直失败。
原代码还存在两处逻辑笔误:
- 用户名不匹配的分支日志写为“Username provided is correct”,语义完全相反
- 密码校验分支打印变量错误,输出了用户名
u而非密码p,日志语义同样写反
修复方案
- 手动解析
Proxy-Authorization头完成认证校验,不直接使用r.BasicAuth() - 认证失败时返回407状态码,同时携带
Proxy-Authenticate响应头,告知客户端使用Basic方式传递认证信息 - 修正日志和判断逻辑的笔误
- 普通HTTP请求和CONNECT隧道请求复用同一套认证逻辑
修复后可直接运行的完整代码如下:
package main import ( "crypto/tls" "encoding/base64" "fmt" "io" "log" "net" "net/http" "net/http/httputil" "strings" "time" ) // 替换为实际的代理账号密码 const ( proxyUser = "USERNAME" proxyPass = "PASSWORD" ) func main() { server := &http.Server{ Addr: "0.0.0.0:8080", ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 调试用:打印完整请求内容 b, err := httputil.DumpRequest(r, true) if err == nil { fmt.Println("dump request:\n", string(b)) } else { fmt.Println("dump request error:", err) } // 读取代理专用认证头 authHeader := r.Header.Get("Proxy-Authorization") if authHeader == "" { w.Header().Set("Proxy-Authenticate", `Basic realm="Proxy Auth Required"`) w.WriteHeader(http.StatusProxyAuthRequired) fmt.Println("missing proxy auth header, return 407") return } // 校验认证类型为Basic const basicPrefix = "Basic " if !strings.HasPrefix(authHeader, basicPrefix) { w.Header().Set("Proxy-Authenticate", `Basic realm="Proxy Auth Required"`) w.WriteHeader(http.StatusProxyAuthRequired) fmt.Println("unsupported auth type, return 407") return } // 解码base64编码的认证信息 decodedCred, err := base64.StdEncoding.DecodeString(authHeader[len(basicPrefix):]) if err != nil { w.Header().Set("Proxy-Authenticate", `Basic realm="Proxy Auth Required"`) w.WriteHeader(http.StatusProxyAuthRequired) fmt.Println("decode auth info error:", err) return } // 拆分用户名和密码 credParts := strings.SplitN(string(decodedCred), ":", 2) if len(credParts) != 2 { w.Header().Set("Proxy-Authenticate", `Basic realm="Proxy Auth Required"`) w.WriteHeader(http.StatusProxyAuthRequired) fmt.Println("invalid credential format") return } inputUser, inputPass := credParts[0], credParts[1] // 校验账号密码 if inputUser != proxyUser { w.WriteHeader(http.StatusProxyAuthRequired) fmt.Printf("invalid username: %s\n", inputUser) return } if inputPass != proxyPass { w.WriteHeader(http.StatusProxyAuthRequired) fmt.Printf("invalid password for user: %s\n", inputUser) return } // 认证通过,按请求类型转发 if r.Method == http.MethodConnect { handleTunneling(w, r) } else { handleHTTP(w, r) } }), // 禁用HTTP/2 TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), } log.Fatal(server.ListenAndServe()) } // 普通HTTP请求转发 func handleHTTP(w http.ResponseWriter, r *http.Request) { r.RequestURI = "" proxy := httputil.NewSingleHostReverseProxy(r.URL) proxy.ServeHTTP(w, r) } // HTTPS CONNECT隧道处理 func handleTunneling(w http.ResponseWriter, r *http.Request) { destConn, err := net.DialTimeout("tcp", r.Host, 10*time.Second) if err != nil { http.Error(w, err.Error(), http.StatusServiceUnavailable) return } w.WriteHeader(http.StatusOK) hijacker, ok := w.(http.Hijacker) if !ok { http.Error(w, "hijack not supported", http.StatusInternalServerError) destConn.Close() return } clientConn, _, err := hijacker.Hijack() if err != nil { http.Error(w, err.Error(), http.StatusServiceUnavailable) destConn.Close() return } // 双向拷贝流量 go func() { defer clientConn.Close() defer destConn.Close() _, _ = io.Copy(destConn, clientConn) }() go func() { defer clientConn.Close() defer destConn.Close() _, _ = io.Copy(clientConn, destConn) }() }
验证注意事项
- 确认FoxyProxy中配置的账号密码和代码中常量完全一致,无多余空格或特殊字符转义问题
- 可通过控制台打印的请求dump内容,确认
Proxy-Authorization头是否被客户端正确携带 - 命令行测试可使用带代理参数的curl命令:
curl -x http://USERNAME:PASSWORD@127.0.0.1:8080 https://example.com,不要直接请求代理服务地址测试
内容的提问来源于stack exchange,提问作者m shiravand
相关产品推荐
相关产品推荐

