Vercel部署的Go Serverless函数调用POST接口返回无效请求排查
问题原因
- 函数入口未校验请求方法
你当前没有对触发函数的入站请求方法做校验,调用方如果以GET方法请求该函数接口,就会出现日志里显示入站请求为GET的情况。 - 目标API请求构造错误
根据OAuth2标准,/connect/token接口要求授权参数放在application/x-www-form-urlencoded格式的请求体中,你当前把所有参数都拼接在了URL查询参数中,且请求体传了nil,完全不符合接口要求,同时敏感参数放在URL查询参数中会被日志系统记录,存在严重的安全泄漏风险。 - 请求头设置错误
- 标准HTTP头没有
Accepts字段,你应该设置的是Content-Type: application/x-www-form-urlencoded,用来告知服务端当前请求体是表单格式 - 即使要设置接收类型,正确的头名是
Accept,值也需要写完整的MIME类型application/x-www-form-urlencoded
- 标准HTTP头没有
- 错误处理不符合Serverless规范
Go的Vercel Serverless函数中禁止直接调用os.Exit(1),该操作会直接终止运行时进程,不仅会导致自定义日志来不及输出,还会让Vercel返回通用的无效请求错误,不会返回你预期的报错信息。 - 参数获取逻辑错误
你获取入站parameter参数时,调用了自己刚刚构造的发往目标API的空请求的req.URL.Query().Get,永远取不到传入的参数值,应该读取触发函数的入站请求r的参数。
修复后的代码
import ( "net/http" "net/url" "os" "io/ioutil" "log" "strings" ) func Fetch(w http.ResponseWriter, r *http.Request) { // 先校验入站请求方法,仅允许POST调用 if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed) w.Write([]byte("only POST method is allowed")) return } client := &http.Client{} // 构造表单参数 q := url.Values{} q.Add("grant_type", "authorization_code") q.Add("client_id", os.Getenv("CLIENT_ID")) q.Add("client_secret", os.Getenv("CLIENT_SECRET")) q.Add("redirect_uri", "https://url.com/callback") // 从入站请求中获取parameter参数 q.Add("parameter", r.URL.Query().Get("parameter")) // 构造发往目标接口的POST请求 req, err := http.NewRequest("POST", "https://auth.truelayer.com/connect/token", nil) if err != nil { log.Printf("create request failed: %v", err) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("internal server error")) return } // 正确设置Content-Type头 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") // 表单内容写入请求体 req.Body = ioutil.NopCloser(strings.NewReader(q.Encode())) // 设置ContentLength避免部分服务端拒绝请求 req.ContentLength = int64(len(q.Encode())) resp, err := client.Do(req) if err != nil { log.Printf("send request failed: %v", err) w.WriteHeader(http.StatusBadGateway) w.Write([]byte("failed to call auth service")) return } defer resp.Body.Close() respBody, err := ioutil.ReadAll(resp.Body) if err != nil { log.Printf("read response failed: %v", err) w.WriteHeader(http.StatusInternalServerError) return } // 透传响应头和内容 for k, v := range resp.Header { w.Header()[k] = v } w.WriteHeader(resp.StatusCode) w.Write(respBody) }
额外注意事项
- 请先确认Vercel环境变量中
CLIENT_ID和CLIENT_SECRET已经正确配置 - 调用该函数的前端请求必须使用POST方法,不要用GET请求调用
- 不要直接返回原始错误信息到前端,避免泄漏服务端敏感配置
内容的提问来源于stack exchange,提问作者Jon Nicholson
相关产品推荐
相关产品推荐

