Files
SproutGate/docs/oauth-providers/gitea.md
2026-05-13 12:19:36 +08:00

4.1 KiB
Raw Blame History

使用 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/userHeaderAuthorization: token {access_token}

Scope 常用:read:user(以你 Gitea 版本界面为准)。

官方概念说明见 Gitea 文档中的 OAuth2 / Applications 章节(不同版本路径可能为站点管理或用户 Applications)。

2. 在 Gitea 中注册应用

  1. 在目标 Gitea 上创建 OAuth2 应用 / 应用OAuth2,填写 Redirect URI(你自有服务的回调,须 HTTPS 生产或本地开发约定)。
  2. 记录 Client IDClient Secret

注意redirect_uri 在授权与换 token 两阶段须一致,且与 Gitea 中登记项一致。

3. Golangoauth2.Endpoint 与示例

golang.org/x/oauth2Config.Endpoint 需手动设为:

endpoint := oauth2.Endpoint{
	AuthURL:  base + "/login/oauth/authorize",
	TokenURL: base + "/login/oauth/access_token",
}

调用 Gitea GET /api/v1/user 时,使用 token 头:Authorization: token + accessTokenGitea 传统写法;若你的版本支持 Bearer 以实际文档为准)。

4. 最小示例片段

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
}

// 示例:构造 ConfigBASE、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/callbackcfg.Exchange 后调用 fetchGiteaUser 即可。整体与 github.md 中示例同构,仅将 Config.Endpoint 换为 giteaEndpoint、将 GitHub Bearer 换为 Gitea 的 token 头。

5. 常见错误

  • 401token 头格式或 API 版本与实例不一致。
  • redirect_uri 不匹配:授权与回调的 host/scheme/端口与 Gitea 中登记的不一致。
  • 多 Gitea 实例时:每个 Clientbase URL 一一对应,勿混用。

6. 安全建议

  • Secret 只放服务端。
  • 以 Gitea 返回的数字 user id 作为绑定主键更稳妥。