oauth2.config 初始化必须填全clientid、clientsecret、redirecturl、authurl、tokenurl五个字段,缺一不可;state校验不可省;callback handler中避免中间件读空r.body;token应通过tokensource自动续期。

oauth2.Config 初始化必须填全五个字段
本地跑不通、跳转后 400 或空白页,八成是 oauth2.Config 缺了某个必填项。它不是可选参数集合,而是 OAuth2 协议强制要求的凭证契约。
-
ClientID和ClientSecret:从 GitHub/Google 等平台申请,注意区分开发环境和生产环境的注册应用 -
RedirectURL:必须和你在 OAuth 提供商后台填写的完全一致,包括协议、域名、端口、结尾斜杠。例如 GitHub 不认http://localhost:8080/callback,得用http://127.0.0.1:8080/callback -
Endpoint.AuthURL和Endpoint.TokenURL:不能只写域名,要带完整路径,比如 Google 是https://accounts.google.com/o/oauth2/v2/auth和https://oauth2.googleapis.com/token
漏掉任意一个,config.AuthCodeURL() 可能生成错误链接,config.Exchange() 则大概率报 invalid_request 或直接 panic。
callback handler 里 state 校验不能省
golang.org/x/oauth2 默认不帮你管 state,但所有主流提供商都强制校验——没它,你的登录流程就暴露在 CSRF 攻击下。
- 跳转前生成随机字符串:
state := generateState()(可用crypto/rand),存进 session 或加密 cookie - 传给授权页:
config.AuthCodeURL(state, oauth2.AccessTypeOffline) - callback 中取并比对:
req.URL.Query().Get("state")必须和 session 里存的完全一致,否则直接http.Error(c.Writer, "state mismatch", http.StatusBadRequest)
跳过这步,用户可能被诱导点击恶意链接,静默完成授权,你却毫无察觉。
Exchange 调用前别让 Gin 中间件读空 r.Body
config.Exchange() 内部会用 http.Post 向令牌端点发请求,需要读取原始 r.Body。如果前面有中间件(比如日志、限流、JWT 解析)调用了 c.Request.Body 或 c.BindJSON(),会导致 body 被提前消耗,Exchange 报 io: read/write on closed pipe 或空响应。
- 确保 callback 路由不挂任何读 body 的中间件
- 若必须做前置处理,用
httputil.DumpRequest备份再还原,或改用c.Request.URL.Query()获取code和state,避免碰 body - 不要在 handler 里手动
io.ReadAll(c.Request.Body),除非你清楚后续没人再需要它
token 存储要用 TokenSource,别存 AccessToken 字符串
拿到 *oauth2.Token 后,如果只存 token.AccessToken 字符串,过期就得手动刷新——而 OAuth2 规范要求自动续期,oauth2.TokenSource 就是干这事的。
- 正确做法:
ts := config.TokenSource(ctx, token),然后每次需要 token 时调ts.Token() - 每个用户应有独立
TokenSource实例,不能全局复用;否则 A 用户 token 过期,B 用户调用也会失败 - 别用全局变量或 map 存
*oauth2.Token指针,它不含刷新逻辑,过期后调用会失败
token 续期逻辑藏在 TokenSource 里,它会自动用 refresh_token 换新 access_token——这个细节,上线后第一周就会暴露出来。











