go 的 x/oauth2 包仅负责协议层令牌交换,不处理登录界面或回调路由,需自行搭建 http 服务接收 code、存储 token、传递 state;否则流程中断。

Go 的 x/oauth2 包本身不处理用户登录界面或回调路由,它只负责协议层的令牌交换和刷新——你必须自己搭 HTTP 服务来接 code、存 token、传 state,否则流程必然中断。
为什么用 oauth2.Config 而不是手拼 URL
手动构造授权 URL 容易漏掉 state 参数或错配 redirect_uri,导致 CSRF 漏洞或“invalid_request”错误。用 oauth2.Config 可以自动注入防重放参数、校验重定向地址、统一 scope 格式。
-
Config的RedirectURL必须和 OAuth2 平台后台配置的完全一致(含末尾斜杠),否则 Google / GitHub 会直接拒绝授权 -
Scopes是字符串切片,不是逗号分隔的单字符串;传[]string{"user:email"}对,传"user:email"会静默失败 -
Endpoint要用平台提供的标准地址,比如 GitHub 是oauth2.Endpoint{AuthURL: "https://github.com/login/oauth/authorize", TokenURL: "https://github.com/login/oauth/access_token"}
config.Exchange() 报 “invalid_grant” 或 “bad_verification_code” 怎么办
这基本是 code 用错了:已被使用过、超时(通常 10 分钟)、或来自不同 RedirectURL 配置的客户端。不会因为 client_secret 错而报这个错(那是 “invalid_client”)。
- 确保
code是从req.URL.Query().Get("code")拿的,且只用一次 - 不要把
code存在日志里或前端 localStorage 中——它是一次性凭证 - 调用
Exchange()前检查req.URL.Query().Get("state")是否匹配你之前生成并存入 session 的值 - 如果用 Gin/Echo 等框架,注意中间件可能提前读取了
req.Body,导致Exchange()内部 POST 失败;此时要加req.Body = ioutil.NopCloser(bytes.NewBuffer(reqBodyBytes))
如何安全保存和复用 *oauth2.Token
Token 不是纯字符串,它包含 AccessToken、RefreshToken、过期时间等字段;直接存 AccessToken 字段会导致无法自动刷新,很快 401。
- 用
json.Marshal(token)序列化整个结构体,而不是只存token.AccessToken - 反序列化后用
config.TokenSource(ctx, token)获取可刷新的TokenSource,再传给http.Client - 别把
RefreshToken存在前端 cookie 或 URL 里;它长期有效,泄露即账户失守 - 若用内存存储(如 map),注意并发写入要加
sync.RWMutex;Redis 存储建议设 TTL 比Expiry多 5 分钟,防时钟漂移
最常被跳过的环节是 state 校验和 RefreshToken 的持久化设计——前者让攻击者能伪造回调,后者让用户每小时就要重新登录一次。这两个点不补上,OAuth2 流程就算跑通了也不算可用。











