# 使用 Gitea 登录:OAuth 2.0 接入指南(Golang) **Gitea** 实例提供与平台绑定的 OAuth2 端点:授权地址、令牌地址都带**你的 Gitea 根 URL** 前缀。本文说明通用注册方式与 Go 中的 `oauth2.Endpoint` 拼法,与具体业务项目无关。 ## 1. 协议与端点 对 Gitea 根地址 `BASE`(无尾斜杠,如 `https://git.example.com`): | 步骤 | URL | |------|-----| | 授权 | `{BASE}/login/oauth/authorize` | | 换 token | `{BASE}/login/oauth/access_token` | | 当前用户 API | `{BASE}/api/v1/user`(Header:`Authorization: token {access_token}`) | Scope 常用:`read:user`(以你 Gitea 版本界面为准)。 官方概念说明见 Gitea 文档中的 OAuth2 / Applications 章节(不同版本路径可能为站点管理或用户 **Applications**)。 ## 2. 在 Gitea 中注册应用 1. 在目标 Gitea 上创建 **OAuth2 应用** / **应用(OAuth2)**,填写 **Redirect URI**(你自有服务的回调,须 HTTPS 生产或本地开发约定)。 2. 记录 **Client ID**、**Client Secret**。 **注意**:`redirect_uri` 在授权与换 token 两阶段须一致,且与 Gitea 中登记项一致。 ## 3. Golang:`oauth2.Endpoint` 与示例 `golang.org/x/oauth2` 的 `Config.Endpoint` 需手动设为: ```go endpoint := oauth2.Endpoint{ AuthURL: base + "/login/oauth/authorize", TokenURL: base + "/login/oauth/access_token", } ``` 调用 Gitea `GET /api/v1/user` 时,使用 **token** 头:`Authorization: token ` + `accessToken`(Gitea 传统写法;若你的版本支持 Bearer 以实际文档为准)。 ## 4. 最小示例片段 ```go package main import ( "context" "encoding/json" "fmt" "io" "net/http" "os" "golang.org/x/oauth2" ) func giteaEndpoint(base string) oauth2.Endpoint { base = trimRightSlash(base) return oauth2.Endpoint{ AuthURL: base + "/login/oauth/authorize", TokenURL: base + "/login/oauth/access_token", } } func trimRightSlash(s string) string { for len(s) > 0 && s[len(s)-1] == '/' { s = s[:len(s)-1] } return s } // 示例:构造 Config(BASE、ClientID、Secret、RedirectURL 从环境或配置读取) func newGiteaConfig() *oauth2.Config { base := os.Getenv("GITEA_BASE_URL") // 如 https://git.example.com return &oauth2.Config{ ClientID: os.Getenv("GITEA_CLIENT_ID"), ClientSecret: os.Getenv("GITEA_CLIENT_SECRET"), RedirectURL: os.Getenv("GITEA_REDIRECT_URL"), Scopes: []string{"read:user"}, Endpoint: giteaEndpoint(base), } } type giteaUser struct { ID int64 `json:"id"` Login string `json:"login"` FullName string `json:"full_name"` Email string `json:"email"` AvatarURL string `json:"avatar_url"` } func fetchGiteaUser(ctx context.Context, base, access string) (*giteaUser, error) { base = trimRightSlash(base) req, _ := http.NewRequestWithContext(ctx, http.MethodGet, base+"/api/v1/user", nil) req.Header.Set("Authorization", "token "+access) res, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer res.Body.Close() b, _ := io.ReadAll(res.Body) if res.StatusCode != http.StatusOK { return nil, fmt.Errorf("gitea: %d %s", res.StatusCode, b) } var u giteaUser if err := json.Unmarshal(b, &u); err != nil { return nil, err } return &u, nil } ``` 在 `main` 中挂载 HTTP 路由:首页生成 `state` 并重定向到 `cfg.AuthCodeURL`;在 `/oauth/gitea/callback` 中 `cfg.Exchange` 后调用 `fetchGiteaUser` 即可。整体与 [github.md](github.md) 中示例同构,仅将 `Config.Endpoint` 换为 `giteaEndpoint`、将 GitHub `Bearer` 换为 Gitea 的 `token` 头。 ## 5. 常见错误 - **401**:token 头格式或 API 版本与实例不一致。 - **redirect_uri 不匹配**:授权与回调的 host/scheme/端口与 Gitea 中登记的不一致。 - 多 Gitea 实例时:每个 **Client** 与 **base URL** 一一对应,勿混用。 ## 6. 安全建议 - Secret 只放服务端。 - 以 Gitea 返回的**数字 user id** 作为绑定主键更稳妥。