Files
user-system/sdk/go/user-management/client.go
long-agent 765a50b7d4 fix: 生产安全修复 + Go SDK + CAS SSO框架
安全修复:
- CRITICAL: SSO重定向URL注入漏洞 - 修复redirect_uri白名单验证
- HIGH: SSO ClientSecret未验证 - 使用crypto/subtle.ConstantTimeCompare验证
- HIGH: 邮件验证码熵值过低(3字节) - 提升到6字节(48位熵)
- HIGH: 短信验证码熵值过低(4字节) - 提升到6字节
- HIGH: Goroutine使用已取消上下文 - auth_email.go使用独立context+超时
- HIGH: SQL LIKE查询注入风险 - permission/role仓库使用escapeLikePattern

新功能:
- Go SDK: sdk/go/user-management/ 完整SDK实现
- CAS SSO框架: internal/auth/cas.go CAS协议支持

其他:
- L1Cache实例问题修复 - AuthMiddleware共享l1Cache
- 设备指纹XSS防护 - 内存存储替代localStorage
- 响应格式协议中间件
- 导出无界查询修复
2026-04-03 17:38:31 +08:00

145 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package userManagement
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
)
// Client API 客户端
type Client struct {
baseURL string
httpClient *http.Client
accessToken string
apiKey string
}
// ClientOption 配置选项
type ClientOption func(*Client)
// WithAPIToken 设置 API Token用于简单认证
func WithAPIToken(token string) ClientOption {
return func(c *Client) {
c.apiKey = token
}
}
// WithAccessToken 设置 Access Token用于已认证请求
func WithAccessToken(token string) ClientOption {
return func(c *Client) {
c.accessToken = token
}
}
// WithHTTPClient 设置自定义 HTTP 客户端
func WithHTTPClient(httpClient *http.Client) ClientOption {
return func(c *Client) {
c.httpClient = httpClient
}
}
// NewClient 创建新的 API 客户端
func NewClient(baseURL string, opts ...ClientOption) *Client {
c := &Client{
baseURL: baseURL,
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
}
for _, opt := range opts {
opt(c)
}
return c
}
// APIResponse 标准 API 响应
type APIResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data json.RawMessage `json:"data,omitempty"`
}
// ErrorResponse 错误响应
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}
func (c *Client) doRequest(ctx context.Context, method, path string, body interface{}) (*http.Response, error) {
u, err := url.JoinPath(c.baseURL, path)
if err != nil {
return nil, fmt.Errorf("failed to join URL: %w", err)
}
var reqBody io.Reader
if body != nil {
jsonData, err := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("failed to marshal request body: %w", err)
}
reqBody = bytes.NewReader(jsonData)
}
req, err := http.NewRequestWithContext(ctx, method, u, reqBody)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
if c.accessToken != "" {
req.Header.Set("Authorization", "Bearer "+c.accessToken)
} else if c.apiKey != "" {
req.Header.Set("X-API-Key", c.apiKey)
}
return c.httpClient.Do(req)
}
func (c *Client) parseResponse(resp *http.Response, result interface{}) error {
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response body: %w", err)
}
if resp.StatusCode >= 400 {
var errResp ErrorResponse
if err := json.Unmarshal(body, &errResp); err == nil {
return fmt.Errorf("API error %d: %s", resp.StatusCode, errResp.Message)
}
return fmt.Errorf("API error %d: %s", resp.StatusCode, string(body))
}
if result == nil {
return nil
}
var apiResp APIResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
return fmt.Errorf("failed to unmarshal response: %w", err)
}
if apiResp.Data != nil {
if err := json.Unmarshal(apiResp.Data, result); err != nil {
return fmt.Errorf("failed to unmarshal data: %w", err)
}
}
return nil
}
// SetAccessToken 设置访问令牌
func (c *Client) SetAccessToken(token string) {
c.accessToken = token
}