124 lines
4.1 KiB
Markdown
124 lines
4.1 KiB
Markdown
# 使用 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** 作为绑定主键更稳妥。
|