自定义用户系统接入文档
1. 系统架构概述
项目采用了抽象的SSO接口设计,支持多种认证服务的接入。通过实现统一的 SSOInterface 接口,可以轻松集成自定义的用户系统。当前已支持两种认证服务:
- Casdoor :开源的身份和访问管理平台
- Paraview :一款闭源符合国标的身份认证系统
2. 核心接口定义
系统中所有SSO服务都必须实现 SSOInterface 接口,定义在 builder/rpc/sso.go 文件中:
// SSO接口定义了所有SSO服务必须实现的方法
type SSOInterface interface {
UpdateUserInfo(ctx context.Context, userInfo *SSOUpdateUserInfo) error
GetUserInfo(ctx context.Context, accessToken string) (*SSOUserInfo, error)
GetOAuthToken(ctx context.Context, code, state string) (*oauth2.Token, error)
DeleteUser(ctx context.Context, uuid string) error
IsExistByName(ctx context.Context, name string) (bool, error)
IsExistByEmail(ctx context.Context, email string) (bool, error)
IsExistByPhone(ctx context.Context, phone string) (bool, error)
CreateInvitation(ctx context.Context, code string) error
GetInvitationCode(ctx context.Context, userUUID string) (string, error)
}
3. 用户系统接入步骤
步骤1: 创建自定义SSO客户端实现
首先,创建一个结构体来实现 SSOInterface 接口。参考现有的 casdoorClientImpl 或 paraviewClientImpl 实现。
package rpc
import (
"context"
"fmt"
// 引入其他必要的包
)
// 自定义SSO客户端实现
type customSSOClientImpl struct {
// 自定义配置字段
endpoint string
clientID string
clientSecret string
// 其他需 要的字段
}
// 确保实现了SSOInterface接口
var (
_ SSOInterface = (*customSSOClientImpl)(nil)
)
步骤2: 实现接口方法
为自定义客户端实现 SSOInterface 中定义的所有方法:
// 获取OAuth令牌
func (c *customSSOClientImpl) GetOAuthToken(ctx context.Context, code, state string) (*oauth2.Token, error) {
// 实现从自定义SSO服务获取令牌的逻辑
// ...
return token, nil
}
// 获取用户信息
func (c *customSSOClientImpl) GetUserInfo(ctx context.Context, accessToken string) (*SSOUserInfo, error) {
// 实现使用访问令牌获取用户信息的逻辑
// ...
return &SSOUserInfo{
Name: "用户名",
Email: "邮箱",
UUID: "用户唯一标识",
RegProvider: "自定义SSO类型",
// 设置其他用户信息字段
}, nil
}
// 实现其他接口方法...
func (c *customSSOClientImpl) UpdateUserInfo(ctx context.Context, userInfo *SSOUpdateUserInfo) error { /* ... */ }
func (c *customSSOClientImpl) DeleteUser(ctx context.Context, uuid string) error { /* ... */ }
func (c *customSSOClientImpl) IsExistByName(ctx context.Context, name string) (bool, error) { /* ... */ }
func (c *customSSOClientImpl) IsExistByEmail(ctx context.Context, email string) (bool, error) { /* ... */ }
func (c *customSSOClientImpl) IsExistByPhone(ctx context.Context, phone string) (bool, error) { /* ... */ }
func (c *customSSOClientImpl) CreateInvitation(ctx context.Context, code string) error { /* ... */ }
func (c *customSSOClientImpl) GetInvitationCode(ctx context.Context, userUUID string) (string, error) { /* ... */ }
步骤3: 创建客户端构造函数
// 创建自定义SSO客户端的构造函数
func NewCustomSSOClient(config *config.Config) SSOInterface {
return &customSSOClientImpl{
endpoint: config.CustomSSO.Endpoint,
clientID: config.CustomSSO.ClientID,
clientSecret: config.CustomSSO.ClientSecret,
// 初始化其他字段
}
}
步骤4: 在配置中添加自定义SSO配置项
// 在Config结构体中添加
SSOType string `env:"STARHUB_SERVER_SSO_TYPE" default:"casdoor"`
// 添加自定义SSO配置
CustomSSO struct {
ClientID string `env:"STARHUB_SERVER_CUSTOM_SSO_CLIENT_ID" default:"client_id"`
ClientSecret string `env:"STARHUB_SERVER_CUSTOM_SSO_CLIENT_SECRET" default:"client_secret"`
Endpoint string `env:"STARHUB_SERVER_CUSTOM_SSO_ENDPOINT" default:"http://localhost:8080"`
// 添加其他需要的配置项
}
步骤5: 修改SSO客户端创建函数
const (
SSOTypeCasdoor = "casdoor"
SSOTypeParaview = "paraview"
SSOTypeCustom = "custom" // 添加自定义SSO类型
)
func NewSSOClient(config *config.Config) (SSOInterface, error) {
switch config.SSOType {
case SSOTypeCasdoor:
// 现有的Casdoor实现
// ...
case SSOTypeParaview:
// 现有的Paraview实现
// ...
case SSOTypeCustom:
// 添加自定义SSO支持
return NewCustomSSOClient(config), nil
default:
return nil, fmt.Errorf("invalid sso type: %s", config.SSOType)
}
}
4. 配置与环境变量
# 指定使用自定义SSO
STARHUB_SERVER_SSO_TYPE=custom
# 自定义SSO配置
STARHUB_SERVER_CUSTOM_SSO_CLIENT_ID=your_client_id
STARHUB_SERVER_CUSTOM_SSO_CLIENT_SECRET=your_client_secret
STARHUB_SERVER_CUSTOM_SSO_ENDPOINT=http://your-sso-server.com
5. 实现示例
以下是一个简化的自定义SSO实现示例,基于OAuth 2.0协议:
package rpc
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"golang.org/x/oauth2"
"opencsg.com/csghub-server/common/config"
"opencsg.com/csghub-server/common/errorx"
)
// 自定义SSO客户端实现
type customSSOClientImpl struct {
endpoint string
clientID string
clientSecret string
redirectURI string
}
// 创建自定义SSO客户端
func NewCustomSSOClient(config *config.Config) SSOInterface {
return &customSSOClientImpl{
endpoint: config.CustomSSO.Endpoint,
clientID: config.CustomSSO.ClientID,
clientSecret: config.CustomSSO.ClientSecret,
redirectURI: config.CustomSSO.RedirectURI,
}
}
// 获取OAuth令牌
func (c *customSSOClientImpl) GetOAuthToken(ctx context.Context, code, state string) (*oauth2.Token, error) {
// 构建令牌请求URL
tokenURL := fmt.Sprintf("%s/oauth2/token", c.endpoint)
// 准备请求体
data := strings.NewReader(fmt.Sprintf("grant_type=authorization_code&code=%s&redirect_uri=%s&client_id=%s&client_secret=%s",
code, c.redirectURI, c.clientID, c.clientSecret))
// 发送请求
resp, err := http.Post(tokenURL, "application/x-www-form-urlencoded", data)
if err != nil {
return nil, errorx.RemoteSvcFail(err, errorx.Ctx().Set("service", "custom-sso"))
}
defer resp.Body.Close()
// 处理响应
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("failed to get token: %s", string(body))
}
// 解析令牌
var token oauth2.Token
if err := json.NewDecoder(resp.Body).Decode(&token); err != nil {
return nil, err
}
return &token, nil
}
// 获取用户信息
func (c *customSSOClientImpl) GetUserInfo(ctx context.Context, accessToken string) (*SSOUserInfo, error) {
// 构建用户信息请求URL
userInfoURL := fmt.Sprintf("%s/api/userinfo", c.endpoint)
// 创建请求
req, err := http.NewRequestWithContext(ctx, "GET", userInfoURL, nil)
if err != nil {
return nil, err
}
// 设置Authorization头
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
// 发送请求
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, errorx.RemoteSvcFail(err, errorx.Ctx().Set("service", "custom-sso"))
}
defer resp.Body.Close()
// 处理响应
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("failed to get user info: %s", string(body))
}
// 解析用户信息
var userData map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&userData); err != nil {
return nil, err
}
// 构建SSOUserInfo对象
return &SSOUserInfo{
Name: userData["username"].(string),
Email: userData["email"].(string),
UUID: userData["id"].(string),
RegProvider: SSOTypeCustom,
// 填充其他字段
}, nil
}
// 实现其他接口方法...
// UpdateUserInfo, DeleteUser, IsExistByName, IsExistByEmail, IsExistByPhone,
// CreateInvitation, GetInvitationCode
6. 注意事项
- 错误处理 :确保实现适当的错误处理逻辑,并使用 errorx.RemoteSvcFail 包装远程服务调用错误
- 安全性 :保护好客户端密钥等敏感信息,避免在日志中泄露
- 数据映射 :确保自定义SSO返回的用户数据正确映射到 SSOUserInfo 结构体
- 并发处理 :确保实现的方法是线程安全的
- 测试 :在接入生产环境前,充分测试所有接口方法 通过以上步骤,您可以成功地将自定义用户系统接入到项目中,实现统一的用户认证和管理功能。