diff --git a/README.md b/README.md index 0cfaedf3473e02d4010c29a8e77072a0a3d347fa..d42f37e5e4b43fd7a6745ce45b4ffde93e72de80 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ A lightweight, high-performance Go authentication and authorization framework, i - 🔐 **Authentication** - Multi-device login, Token management - 🛡️ **Authorization** - Fine-grained permission control, wildcard support (`*`, `user:*`, `user:*:view`) +- 🛣️ **Path-Based Auth** - Flexible path-based authentication with Ant-style wildcards - 👥 **Role Management** - Flexible role authorization mechanism - 🚫 **Account Ban** - Temporary/permanent account disabling - 👢 **Kickout** - Force user logout, multi-device mutual exclusion @@ -34,41 +35,45 @@ A lightweight, high-performance Go authentication and authorization framework, i ```bash # Import only the framework integration (includes core + stputil automatically) -go get github.com/click33/sa-token-go/integrations/gin@v0.1.5 # Gin framework +go get github.com/click33/sa-token-go/integrations/gin@latest # Gin framework # or -go get github.com/click33/sa-token-go/integrations/echo@v0.1.5 # Echo framework +go get github.com/click33/sa-token-go/integrations/echo@latest # Echo framework # or -go get github.com/click33/sa-token-go/integrations/fiber@v0.1.5 # Fiber framework +go get github.com/click33/sa-token-go/integrations/fiber@latest # Fiber framework # or -go get github.com/click33/sa-token-go/integrations/chi@v0.1.5 # Chi framework +go get github.com/click33/sa-token-go/integrations/chi@latest # Chi framework # or -go get github.com/click33/sa-token-go/integrations/gf@v0.1.5 # GoFrame framework +go get github.com/click33/sa-token-go/integrations/gf@latest # GoFrame framework # or -go get github.com/click33/sa-token-go/integrations/kratos@v0.1.5 # Kratos framework +go get github.com/click33/sa-token-go/integrations/kratos@latest # Kratos framework +# or +go get github.com/click33/sa-token-go/integrations/hertz@latest # Hertz framework +``` # Storage module (choose one) -go get github.com/click33/sa-token-go/storage/memory@v0.1.5 # Memory storage (dev) -go get github.com/click33/sa-token-go/storage/redis@v0.1.5 # Redis storage (prod) +go get github.com/click33/sa-token-go/storage/memory@latest # Memory storage (dev) +go get github.com/click33/sa-token-go/storage/redis@latest # Redis storage (prod) ``` #### Option 2: Separate Import ```bash # Core modules -go get github.com/click33/sa-token-go/core@v0.1.5 -go get github.com/click33/sa-token-go/stputil@v0.1.5 +go get github.com/click33/sa-token-go/core@vlatest +go get github.com/click33/sa-token-go/stputil@vlatest # Storage module (choose one) -go get github.com/click33/sa-token-go/storage/memory@v0.1.5 # Memory storage (dev) -go get github.com/click33/sa-token-go/storage/redis@v0.1.5 # Redis storage (prod) +go get github.com/click33/sa-token-go/storage/memory@latest # Memory storage (dev) +go get github.com/click33/sa-token-go/storage/redis@latest # Redis storage (prod) # Framework integration (optional) -go get github.com/click33/sa-token-go/integrations/gin@v0.1.5 # Gin framework -go get github.com/click33/sa-token-go/integrations/echo@v0.1.5 # Echo framework -go get github.com/click33/sa-token-go/integrations/fiber@v0.1.5 # Fiber framework -go get github.com/click33/sa-token-go/integrations/chi@v0.1.5 # Chi framework -go get github.com/click33/sa-token-go/integrations/gf@v0.1.5 # GoFrame framework -go get github.com/click33/sa-token-go/integrations/kratos@v0.1.5 # Kratos framework +go get github.com/click33/sa-token-go/integrations/gin@latest # Gin framework +go get github.com/click33/sa-token-go/integrations/echo@latest # Echo framework +go get github.com/click33/sa-token-go/integrations/fiber@latest # Fiber framework +go get github.com/click33/sa-token-go/integrations/chi@latest # Chi framework +go get github.com/click33/sa-token-go/integrations/gf@latest # GoFrame framework +go get github.com/click33/sa-token-go/integrations/kratos@latest# Kratos framework +go get github.com/click33/sa-token-go/integrations/hertz@latest # Hertz framework ``` ### ⚡ Minimal Usage (One-line Initialization) @@ -105,7 +110,7 @@ func init() { ___/ / /_/ / / / / /_/ / ,< / __/ / / /_____/ /_/ / /_/ / /____/\__,_/ /_/ \____/_/|_|\___/_/ /_/ \____/\____/ -:: Sa-Token-Go :: (v0.1.5) +:: Sa-Token-Go :: (v0.1.7) :: Go Version :: go1.21.0 :: GOOS/GOARCH :: linux/amd64 @@ -377,6 +382,10 @@ r.Get("/user", sachi.CheckLogin(), handler) // Kratos import sakratos "github.com/click33/sa-token-go/integrations/kratos" // Use Plugin.Server() as middleware + +// Hertz +import sahertz "github.com/click33/sa-token-go/integrations/hertz" +h.GET("/user", sahertz.CheckLogin(), handler) ``` ## 🎨 Advanced Features @@ -575,6 +584,7 @@ sa-token-go/ - [Quick Start](docs/tutorial/quick-start.md) - Get started in 5 minutes - [Authentication](docs/guide/authentication.md) - Authentication guide +- [Path-Based Auth](docs/guide/path-auth.md) - Path-based authentication guide - [Permission](docs/guide/permission.md) - Permission system - [Annotations](docs/guide/annotation.md) - Decorator pattern guide - [Event Listener](docs/guide/listener.md) - Event system guide diff --git a/README_zh.md b/README_zh.md index 622a0b0ca66d2a7b9f5d19399c1ed608dd378a59..5e9d8bf7b5a832d3354dc63fd8313ee7f67fd637 100644 --- a/README_zh.md +++ b/README_zh.md @@ -11,6 +11,7 @@ - 🔐 **登录认证** - 支持多设备登录、Token管理 - 🛡️ **权限验证** - 细粒度权限控制、通配符支持(`*`, `user:*`, `user:*:view`) +- 🛣️ **路径鉴权** - 灵活的路径鉴权、支持Ant风格通配符 - 👥 **角色管理** - 灵活的角色授权机制 - 🚫 **账号封禁** - 临时/永久封禁功能 - 👢 **踢人下线** - 强制用户下线、多端互斥登录 @@ -24,6 +25,12 @@ - 🔄 **Refresh Token** - 刷新令牌机制、无感刷新 - 🔐 **OAuth2** - 完整的OAuth2授权码模式实现 + +## 💬 微信交流群 + +sa-token-go 微信交流群 + + ## 🚀 快速开始 ### 📥 安装 @@ -34,41 +41,44 @@ ```bash # 只导入框架集成包(自动包含 core + stputil) -go get github.com/click33/sa-token-go/integrations/gin@v0.1.5 # Gin框架 +go get github.com/click33/sa-token-go/integrations/gin@latest # Gin框架 +# 或 +go get github.com/click33/sa-token-go/integrations/echo@latest # Echo框架 # 或 -go get github.com/click33/sa-token-go/integrations/echo@v0.1.5 # Echo框架 +go get github.com/click33/sa-token-go/integrations/fiber@latest # Fiber框架 # 或 -go get github.com/click33/sa-token-go/integrations/fiber@v0.1.5 # Fiber框架 +go get github.com/click33/sa-token-go/integrations/chi@latest # Chi框架 # 或 -go get github.com/click33/sa-token-go/integrations/chi@v0.1.5 # Chi框架 +go get github.com/click33/sa-token-go/integrations/gf@latest # GoFrame框架 # 或 -go get github.com/click33/sa-token-go/integrations/gf@v0.1.5 # GoFrame框架 +go get github.com/click33/sa-token-go/integrations/kratos@latest# Kratos框架 # 或 -go get github.com/click33/sa-token-go/integrations/kratos@v0.1.5 # Kratos框架 +go get github.com/click33/sa-token-go/integrations/hertz@latest # Hertz框架 # 存储模块(选一个) -go get github.com/click33/sa-token-go/storage/memory@v0.1.5 # 内存存储(开发) -go get github.com/click33/sa-token-go/storage/redis@v0.1.5 # Redis存储(生产) +go get github.com/click33/sa-token-go/storage/memory@latest # 内存存储(开发) +go get github.com/click33/sa-token-go/storage/redis@latest # Redis存储(生产) ``` #### 方式二:分开导入 ```bash # 核心模块 -go get github.com/click33/sa-token-go/core@v0.1.5 -go get github.com/click33/sa-token-go/stputil@v0.1.5 +go get github.com/click33/sa-token-go/core@vlatest +go get github.com/click33/sa-token-go/stputil@vlatest # 存储模块(选一个) -go get github.com/click33/sa-token-go/storage/memory@v0.1.5 # 内存存储(开发) -go get github.com/click33/sa-token-go/storage/redis@v0.1.5 # Redis存储(生产) +go get github.com/click33/sa-token-go/storage/memory@latest # 内存存储(开发) +go get github.com/click33/sa-token-go/storage/redis@latest # Redis存储(生产) # 框架集成(可选) -go get github.com/click33/sa-token-go/integrations/gin@v0.1.5 # Gin框架 -go get github.com/click33/sa-token-go/integrations/echo@v0.1.5 # Echo框架 -go get github.com/click33/sa-token-go/integrations/fiber@v0.1.5 # Fiber框架 -go get github.com/click33/sa-token-go/integrations/chi@v0.1.5 # Chi框架 -go get github.com/click33/sa-token-go/integrations/gf@v0.1.5 # GoFrame框架 -go get github.com/click33/sa-token-go/integrations/kratos@v0.1.5 # Kratos框架 +go get github.com/click33/sa-token-go/integrations/gin@latest # Gin框架 +go get github.com/click33/sa-token-go/integrations/echo@latest # Echo框架 +go get github.com/click33/sa-token-go/integrations/fiber@latest # Fiber框架 +go get github.com/click33/sa-token-go/integrations/chi@latest # Chi框架 +go get github.com/click33/sa-token-go/integrations/gf@latest # GoFrame框架 +go get github.com/click33/sa-token-go/integrations/kratos@latest# Kratos框架 +go get github.com/click33/sa-token-go/integrations/hertz@latest # Hertz框架 ``` ### ⚡ 超简洁使用(一行初始化) @@ -105,7 +115,7 @@ func init() { ___/ / /_/ / / / / /_/ / ,< / __/ / / /_____/ /_/ / /_/ / /____/\__,_/ /_/ \____/_/|_|\___/_/ /_/ \____/\____/ -:: Sa-Token-Go :: (v0.1.5) +:: Sa-Token-Go :: (v0.1.7) :: Go Version :: go1.21.0 :: GOOS/GOARCH :: linux/amd64 @@ -377,6 +387,10 @@ r.Get("/user", sachi.CheckLogin(), handler) // Kratos import sakratos "github.com/click33/sa-token-go/integrations/kratos" // 使用 Plugin.Server() 作为中间件 + +// Hertz +import sahertz "github.com/click33/sa-token-go/integrations/hertz" +h.GET("/user", sahertz.CheckLogin(), handler) ``` ## 🎨 高级特性 @@ -583,6 +597,7 @@ sa-token-go/ - [快速开始](docs/tutorial/quick-start_zh.md) - 5分钟上手 - [登录认证](docs/guide/authentication_zh.md) - 登录认证详解 +- [路径鉴权](docs/guide/path-auth_zh.md) - 路径鉴权详解 - [权限验证](docs/guide/permission_zh.md) - 权限系统详解 - [注解使用](docs/guide/annotation_zh.md) - 装饰器模式详解 - [事件监听](docs/guide/listener_zh.md) - 事件系统详解 diff --git a/core/banner/banner.go b/core/banner/banner.go index cd1ae28ed62514e0f068681ed846ca0f327200dc..5f746e512004465c0d52ed4fa12ef42750fbda44 100644 --- a/core/banner/banner.go +++ b/core/banner/banner.go @@ -6,11 +6,9 @@ import ( "strings" "github.com/click33/sa-token-go/core/config" + "github.com/click33/sa-token-go/core/version" ) -// Version version number | 版本号 -const Version = "0.1.1" - // Banner startup banner | 启动横幅 const Banner = ` _____ ______ __ ______ @@ -33,7 +31,7 @@ const ( // Print prints startup banner | 打印启动横幅 func Print() { - fmt.Printf(Banner, Version) + fmt.Printf(Banner, version.Version) fmt.Printf(":: Go Version :: %s\n", runtime.Version()) fmt.Printf(":: GOOS/GOARCH :: %s/%s\n", runtime.GOOS, runtime.GOARCH) fmt.Println() diff --git a/core/banner/banner_test.go b/core/banner/banner_test.go index 90027d49d7ac93b8db3fabd8b264eba8e119ae3c..aaf44d1fd71134b5b1859a1be55c2c6345ca3d6e 100644 --- a/core/banner/banner_test.go +++ b/core/banner/banner_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/click33/sa-token-go/core/config" + "github.com/click33/sa-token-go/core/version" ) // captureOutput captures stdout output for testing @@ -37,8 +38,8 @@ func TestPrint(t *testing.T) { t.Error("Output should contain 'Sa-Token-Go'") } - if !strings.Contains(output, Version) { - t.Errorf("Output should contain version %s", Version) + if !strings.Contains(output, version.Version) { + t.Errorf("Output should contain version %s", version.Version) } if !strings.Contains(output, "Go Version") { @@ -206,7 +207,7 @@ func TestPrintWithConfig(t *testing.T) { contains: []string{ "Configuration", "Token Name", - "sa-token", + "satoken", "Token Style", "uuid", "Token Timeout", @@ -215,10 +216,11 @@ func TestPrintWithConfig(t *testing.T) { "Concurrent", "Share Token", "Max Login Count", - "Read From Header", - "Read From Cookie", - "Read From Body", - "Logging", + "Read From", + "Header", + "Cookie MaxAge", + "Cookie Secure", + "Cookie HttpOnly", }, }, { @@ -248,11 +250,9 @@ func TestPrintWithConfig(t *testing.T) { "jwt-token", "jwt", "3600 seconds", - "JWT Secret", + "JWT Secret Key", "*** (configured)", - "Cookie Path", - "/api", - "Cookie SameSite", + "Cookie MaxAge", "Cookie HttpOnly", "Cookie Secure", }, @@ -283,8 +283,8 @@ func TestPrintWithConfig(t *testing.T) { CookieConfig: &config.CookieConfig{}, }, contains: []string{ - "JWT Secret", - "Not Set", + "JWT Secret Key", + "*** (configured)", }, }, } diff --git a/core/builder/builder.go b/core/builder/builder.go index a06678d6fe2b5640ea014d9fea3370a91b48a7a8..a2827abda122a194af3c15a6c29ca99fde93f9eb 100644 --- a/core/builder/builder.go +++ b/core/builder/builder.go @@ -311,9 +311,12 @@ func (b *Builder) Validate() error { return fmt.Errorf("MaxRefresh must be >= -1, got: %d", b.maxRefresh) } - // Check MaxRefresh does not exceed Timeout + // Adjust MaxRefresh if it exceeds Timeout | 如果 MaxRefresh 大于 Timeout,则自动调整为 Timeout/2 if b.timeout != config.NoLimit && b.maxRefresh > b.timeout { - return fmt.Errorf("MaxRefresh (%d) cannot be greater than Timeout (%d)", b.maxRefresh, b.timeout) + b.maxRefresh = b.timeout / 2 + if b.maxRefresh < 1 { + b.maxRefresh = 1 + } } // Check RenewInterval diff --git a/core/config/config.go b/core/config/config.go index 56ccad6f095bb062a6994cd596203e8c8a133c9d..a6cde61cc43254ee2bf54afbadd71208847fb330 100644 --- a/core/config/config.go +++ b/core/config/config.go @@ -210,9 +210,12 @@ func (c *Config) Validate() error { return fmt.Errorf("MaxRefresh must be >= -1, got: %d", c.MaxRefresh) } - // Check MaxRefresh does not exceed Timeout + // Adjust MaxRefresh if it exceeds Timeout | 如果 MaxRefresh 大于 Timeout,则自动调整为 Timeout/2 if c.Timeout != NoLimit && c.MaxRefresh > c.Timeout { - return fmt.Errorf("MaxRefresh (%d) cannot be greater than Timeout (%d)", c.MaxRefresh, c.Timeout) + c.MaxRefresh = c.Timeout / 2 + if c.MaxRefresh < 1 { + c.MaxRefresh = 1 + } } // Check RenewInterval diff --git a/core/errors.go b/core/errors.go index 0a80c2d6d448f8f9ccc008208901339f819cdce6..428deb2cd3b9b85eb1ff42910e852fd13b539726 100644 --- a/core/errors.go +++ b/core/errors.go @@ -63,6 +63,16 @@ var ( ErrMaxLoginCount = fmt.Errorf("max login limit: maximum number of concurrent logins reached") ) +// ============ Path Authentication Errors | 路径鉴权错误 ============ + +var ( + // ErrPathAuthRequired indicates path authentication is required | 路径需要鉴权 + ErrPathAuthRequired = fmt.Errorf("path authentication required: this path requires authentication") + + // ErrPathNotAllowed indicates path is not allowed | 路径不允许访问 + ErrPathNotAllowed = fmt.Errorf("path not allowed: access to this path is forbidden") +) + // ============ System Errors | 系统错误 ============ var ( @@ -165,6 +175,18 @@ func NewAccountDisabledError(loginID string) *SaTokenError { WithContext("loginID", loginID) } +// NewPathAuthRequiredError Creates a path authentication required error | 创建路径需要鉴权错误 +func NewPathAuthRequiredError(path string) *SaTokenError { + return NewError(CodePathAuthRequired, "path authentication required", ErrPathAuthRequired). + WithContext("path", path) +} + +// NewPathNotAllowedError Creates a path not allowed error | 创建路径不允许访问错误 +func NewPathNotAllowedError(path string) *SaTokenError { + return NewError(CodePathNotAllowed, "path not allowed", ErrPathNotAllowed). + WithContext("path", path) +} + // ============ Error Checking Helpers | 错误检查辅助函数 ============ // IsNotLoginError Checks if error is a not login error | 检查是否为未登录错误 @@ -204,6 +226,8 @@ const ( CodeBadRequest = 400 // Bad request | 错误的请求 CodeNotLogin = 401 // Not authenticated | 未认证 CodePermissionDenied = 403 // Permission denied | 权限不足 + CodePathAuthRequired = 401 // Path authentication required | 路径需要鉴权 + CodePathNotAllowed = 403 // Path not allowed | 路径不允许访问 CodeNotFound = 404 // Resource not found | 资源未找到 CodeServerError = 500 // Internal server error | 服务器内部错误 diff --git a/core/router/router.go b/core/router/router.go new file mode 100644 index 0000000000000000000000000000000000000000..21eaff4fab5b0261cd837e3ba7c1eb9e24bf5599 --- /dev/null +++ b/core/router/router.go @@ -0,0 +1,192 @@ +package router + +import ( + "strings" + "github.com/click33/sa-token-go/core/manager" +) + +// MatchPath matches a path against a pattern (Ant-style wildcard) | 匹配路径与模式(Ant风格通配符) +// Supported patterns: +// - "/**": Match all paths | 匹配所有路径 +// - "/api/**": Match all paths starting with "/api/" | 匹配所有以"/api/"开头的路径 +// - "/api/*": Match single-level paths under "/api/" | 匹配"/api/"下的单级路径 +// - "*.html": Match paths ending with ".html" | 匹配以".html"结尾的路径 +// - "/exact": Exact match | 精确匹配 +func MatchPath(path, pattern string) bool { + if pattern == "/**" { + return true + } + + if strings.HasSuffix(pattern, "/**") { + prefix := pattern[:len(pattern)-3] + return strings.HasPrefix(path, prefix) + } + + if strings.HasPrefix(pattern, "*") { + suffix := pattern[1:] + return strings.HasSuffix(path, suffix) + } + + if strings.HasSuffix(pattern, "/*") { + prefix := pattern[:len(pattern)-2] + if strings.HasPrefix(path, prefix) { + suffix := path[len(prefix):] + if suffix == "" || suffix == "/" { + return true + } + return !strings.Contains(suffix[1:], "/") + } + return false + } + + return path == pattern +} + +// MatchAny checks if path matches any pattern in the list | 检查路径是否匹配列表中的任意模式 +func MatchAny(path string, patterns []string) bool { + for _, pattern := range patterns { + if MatchPath(path, pattern) { + return true + } + } + return false +} + +// NeedAuth determines if authentication is needed for a path | 判断路径是否需要鉴权 +// Returns true if path matches include patterns but not exclude patterns | 如果路径匹配包含模式但不匹配排除模式,返回true +func NeedAuth(path string, include, exclude []string) bool { + return MatchAny(path, include) && !MatchAny(path, exclude) +} + +// PathAuthConfig path-based authentication configuration | 基于路径的鉴权配置 +// Configure which paths require authentication and which are excluded | 配置哪些路径需要鉴权,哪些路径被排除 +type PathAuthConfig struct { + // Include paths that require authentication (include patterns) | 需要鉴权的路径(包含模式) + Include []string + // Exclude paths excluded from authentication (exclude patterns) | 排除鉴权的路径(排除模式) + Exclude []string + // Validator optional login ID validator function | 可选的登录ID验证函数 + Validator func(loginID string) bool +} + +// NewPathAuthConfig creates a new path authentication configuration | 创建新的路径鉴权配置 +func NewPathAuthConfig() *PathAuthConfig { + return &PathAuthConfig{ + Include: []string{}, + Exclude: []string{}, + Validator: nil, + } +} + +// SetInclude sets paths that require authentication | 设置需要鉴权的路径 +func (c *PathAuthConfig) SetInclude(patterns []string) *PathAuthConfig { + c.Include = patterns + return c +} + +// SetExclude sets paths excluded from authentication | 设置排除鉴权的路径 +func (c *PathAuthConfig) SetExclude(patterns []string) *PathAuthConfig { + c.Exclude = patterns + return c +} + +// SetValidator sets a custom login ID validator function | 设置自定义的登录ID验证函数 +func (c *PathAuthConfig) SetValidator(validator func(loginID string) bool) *PathAuthConfig { + c.Validator = validator + return c +} + +// Check checks if a path requires authentication | 检查路径是否需要鉴权 +func (c *PathAuthConfig) Check(path string) bool { + return NeedAuth(path, c.Include, c.Exclude) +} + +// ValidateLoginID validates a login ID using the configured validator | 使用配置的验证器验证登录ID +func (c *PathAuthConfig) ValidateLoginID(loginID string) bool { + if c.Validator == nil { + return true + } + return c.Validator(loginID) +} + +// AuthResult authentication result after processing | 处理后的鉴权结果 +type AuthResult struct { + // NeedAuth whether authentication is required for this path | 此路径是否需要鉴权 + NeedAuth bool + // Token extracted token value | 提取的token值 + Token string + // TokenInfo token information if valid | 如果有效则包含token信息 + TokenInfo *manager.TokenInfo + // IsValid whether the token is valid | token是否有效 + IsValid bool +} + +// ShouldReject checks if the request should be rejected | 检查请求是否应该被拒绝 +func (r *AuthResult) ShouldReject() bool { + return r.NeedAuth && (!r.IsValid || r.Token == "") +} + +// LoginID gets the login ID from token info | 从token信息中获取登录ID +func (r *AuthResult) LoginID() string { + if r.TokenInfo != nil { + return r.TokenInfo.LoginID + } + return "" +} + +// ProcessAuth processes authentication for a request path | 处理请求路径的鉴权 +// This function checks if the path requires authentication, validates the token, +// and returns an AuthResult with all relevant information | 此函数检查路径是否需要鉴权,验证token,并返回包含所有相关信息的AuthResult +func ProcessAuth(path, tokenStr string, config *PathAuthConfig, mgr *manager.Manager) *AuthResult { + needAuth := config.Check(path) + + token := tokenStr + isValid := false + var tokenInfo *manager.TokenInfo + + if token != "" { + isValid = mgr.IsLogin(token) + if isValid { + info, err := mgr.GetTokenInfo(token) + if err == nil && info != nil { + tokenInfo = info + if needAuth && config.Validator != nil { + isValid = config.ValidateLoginID(tokenInfo.LoginID) + } + } + } + } + + return &AuthResult{ + NeedAuth: needAuth, + Token: token, + TokenInfo: tokenInfo, + IsValid: isValid, + } +} + +// PathAuthHandler interface for path authentication handlers | 路径鉴权处理器接口 +type PathAuthHandler interface { + GetPath() string + GetToken() string + GetManager() *manager.Manager + GetPathAuthConfig() *PathAuthConfig +} + +// CheckPathAuth checks path authentication using the handler interface | 使用处理器接口检查路径鉴权 +// Returns true if authentication is required and should be rejected | 如果需要鉴权且应该被拒绝,返回true +func CheckPathAuth(handler PathAuthHandler) bool { + path := handler.GetPath() + token := handler.GetToken() + manager := handler.GetManager() + config := handler.GetPathAuthConfig() + + if config == nil { + config = NewPathAuthConfig().SetInclude([]string{"/**"}) + } + + result := ProcessAuth(path, token, config, manager) + + return result.ShouldReject() +} + diff --git a/core/satoken.go b/core/satoken.go index fff2046ae2579713b261df38e176278170725211..851dcec9642513bd00ab230a5ae80d75066fa59f 100644 --- a/core/satoken.go +++ b/core/satoken.go @@ -10,14 +10,16 @@ import ( "github.com/click33/sa-token-go/core/listener" "github.com/click33/sa-token-go/core/manager" "github.com/click33/sa-token-go/core/oauth2" + "github.com/click33/sa-token-go/core/router" "github.com/click33/sa-token-go/core/security" "github.com/click33/sa-token-go/core/session" "github.com/click33/sa-token-go/core/token" "github.com/click33/sa-token-go/core/utils" + "github.com/click33/sa-token-go/core/version" ) // Version Sa-Token-Go version | Sa-Token-Go版本 -const Version = "0.1.3" +const Version = version.Version // ============ Exported Types | 导出的类型 ============ // Export main types and functions for external use | 导出主要类型和函数,方便外部使用 @@ -57,6 +59,8 @@ type ( OAuth2Client = oauth2.Client OAuth2AccessToken = oauth2.AccessToken OAuth2GrantType = oauth2.GrantType + PathAuthConfig = router.PathAuthConfig + AuthResult = router.AuthResult ) // Adapter interfaces | 适配器接口 @@ -119,6 +123,13 @@ var ( // Pattern matching | 模式匹配 MatchPattern = utils.MatchPattern + // Router utilities | 路由工具 + MatchPath = router.MatchPath + MatchAny = router.MatchAny + NeedAuth = router.NeedAuth + ProcessAuth = router.ProcessAuth + NewPathAuthConfig = router.NewPathAuthConfig + // Duration utilities | 时长工具 FormatDuration = utils.FormatDuration ParseDuration = utils.ParseDuration diff --git a/core/security/refresh_token.go b/core/security/refresh_token.go index 500761c1e4263c315fd81ff685d40ed2f28ee460..797eb6630615743668eefea578f01a7a2320aa43 100644 --- a/core/security/refresh_token.go +++ b/core/security/refresh_token.go @@ -110,12 +110,6 @@ func (rtm *RefreshTokenManager) GenerateTokenPair(loginID, device string, access } } - // Save token-loginID mapping (符合 Java sa-token 设计) | 保存 Token-LoginID 映射 - tokenKey := rtm.getTokenKey(accessToken) - if err := rtm.storage.Set(tokenKey, loginID, rtm.accessTTL); err != nil { - return nil, fmt.Errorf("failed to save token: %w", err) - } - // Generate refresh token | 生成刷新令牌 refreshTokenBytes := make([]byte, RefreshTokenLength) if _, err := rand.Read(refreshTokenBytes); err != nil { @@ -184,10 +178,14 @@ func (rtm *RefreshTokenManager) RefreshAccessToken(refreshToken string) (*Refres // Update access token info | 更新访问令牌信息 oldInfo.AccessToken = newAccessToken - // Save token-loginID mapping (符合 Java sa-token 设计) | 保存 Token-LoginID 映射 - tokenKey := rtm.getTokenKey(newAccessToken) - if err := rtm.storage.Set(tokenKey, oldInfo.LoginID, rtm.accessTTL); err != nil { - return nil, fmt.Errorf("failed to save token: %w", err) + // Copy original token storage value to new access token key, to keep JSON TokenInfo format + // 复制原 access token 的存储值到新的 access token 键,保持 JSON TokenInfo 格式,避免破坏 IsLogin/CheckLogin + oldTokenKey := rtm.getTokenKey(oldInfo.AccessToken) + if data, err := rtm.storage.Get(oldTokenKey); err == nil && data != nil { + newTokenKey := rtm.getTokenKey(newAccessToken) + if err := rtm.storage.Set(newTokenKey, data, rtm.accessTTL); err != nil { + return nil, fmt.Errorf("failed to save new access token: %w", err) + } } // Update storage | 更新存储 diff --git a/core/version/version.go b/core/version/version.go new file mode 100644 index 0000000000000000000000000000000000000000..ee561755ef51031948533f1f1f673ae7b51e9011 --- /dev/null +++ b/core/version/version.go @@ -0,0 +1,6 @@ +package version + +// Version system level version number | 系统级版本号 +// This is the global version of Sa-Token-Go, modify this value to update the version across the entire project +// 这是 Sa-Token-Go 的全局版本号,修改此值可更新整个项目的版本 +const Version = "0.1.8" diff --git a/docs/.DS_Store b/docs/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 Binary files /dev/null and b/docs/.DS_Store differ diff --git a/docs/IMG_3976.JPG b/docs/IMG_3976.JPG deleted file mode 100644 index 00e77f26dedaadc8b08a5eb5813d6b1f535f2a66..0000000000000000000000000000000000000000 Binary files a/docs/IMG_3976.JPG and /dev/null differ diff --git a/docs/guide/path-auth.md b/docs/guide/path-auth.md new file mode 100644 index 0000000000000000000000000000000000000000..b32e390a8d143406491b7244c123473ca805ab99 --- /dev/null +++ b/docs/guide/path-auth.md @@ -0,0 +1,551 @@ +# Path-Based Authentication + +Path-based authentication allows you to configure which paths require authentication and which paths are excluded, providing flexible access control for your application. + +## Features + +- **Ant-style wildcard patterns** - Support for `/**`, `/*`, `*.html` patterns +- **Include/Exclude configuration** - Fine-grained control over which paths need authentication +- **Custom validators** - Optional login ID validation functions +- **Framework integration** - Works seamlessly with all supported frameworks +- **Token extraction** - Automatically extracts tokens from headers and cookies + +## Pattern Matching + +The path matching supports Ant-style wildcards: + +- `/**` - Matches all paths +- `/api/**` - Matches all paths starting with `/api/` +- `/api/*` - Matches single-level paths under `/api/` (e.g., `/api/user`, but not `/api/user/profile`) +- `*.html` - Matches paths ending with `.html` +- `/exact` - Exact path match + +### Pattern Examples + +```go +// Match all paths +"/**" + +// Match all API paths +"/api/**" + +// Match single-level API paths +"/api/*" + +// Match static files +"*.html" +"*.css" +"*.js" + +// Match specific paths +"/login" +"/logout" +"/public/**" +``` + +## Usage + +### Basic Configuration + +The simplest way to use path-based authentication is through middleware: + +```go +package main + +import ( + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/integrations/gin" + "github.com/click33/sa-token-go/storage/memory" +) + +func main() { + // Initialize manager + manager := core.NewBuilder(). + Storage(memory.NewStorage()). + TokenName("Authorization"). + Timeout(86400). + Build() + + // Create path authentication configuration + config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). // Paths that require authentication + SetExclude([]string{"/api/public/**"}) // Paths excluded from authentication + + // Create plugin and use middleware + plugin := gin.NewPlugin(manager) + r := gin.Default() + + // Apply path authentication middleware + r.Use(plugin.PathAuthMiddleware(config)) + + // Your routes + r.GET("/api/user/info", getUserInfo) + r.GET("/api/public/status", getStatus) // This path is excluded + + r.Run(":8080") +} +``` + +### Multiple Include/Exclude Patterns + +You can specify multiple patterns for more complex scenarios: + +```go +config := core.NewPathAuthConfig(). + SetInclude([]string{ + "/api/**", // All API paths + "/admin/**", // All admin paths + "/user/profile", // Specific user profile path + }). + SetExclude([]string{ + "/api/public/**", // Public API paths + "/api/auth/login", // Login endpoint + "/api/auth/register", // Register endpoint + "*.html", // Static HTML files + "*.css", // CSS files + "*.js", // JavaScript files + }) +``` + +### With Custom Validator + +You can add custom validation logic for login IDs: + +```go +config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{"/api/public/**"}). + SetValidator(func(loginID string) bool { + // Custom validation logic + // For example, check if user is banned + if loginID == "banned_user" { + return false + } + + // Check if user account is active + // You can query your database here + // return isUserActive(loginID) + + return true + }) +``` + +### Complete Example with Gin + +```go +package main + +import ( + "net/http" + + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/integrations/gin" + "github.com/click33/sa-token-go/storage/memory" + "github.com/gin-gonic/gin" +) + +func main() { + // Initialize Sa-Token manager + manager := core.NewBuilder(). + Storage(memory.NewStorage()). + TokenName("Authorization"). + Timeout(86400). + Build() + + // Configure path authentication + pathAuthConfig := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{ + "/api/auth/login", + "/api/auth/register", + "/api/public/**", + }) + + // Create Gin router + r := gin.Default() + + // Create plugin + plugin := gin.NewPlugin(manager) + + // Apply path authentication middleware + r.Use(plugin.PathAuthMiddleware(pathAuthConfig)) + + // Public routes (excluded from auth) + r.POST("/api/auth/login", plugin.LoginHandler) + r.POST("/api/auth/register", registerHandler) + r.GET("/api/public/status", getStatus) + + // Protected routes (require authentication) + api := r.Group("/api") + { + api.GET("/user/info", getUserInfo) + api.GET("/user/profile", getUserProfile) + api.POST("/user/update", updateUser) + } + + r.Run(":8080") +} + +func getUserInfo(c *gin.Context) { + // Get login ID from context (set by PathAuthMiddleware) + loginID, exists := c.Get("loginID") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "not authenticated"}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "loginID": loginID, + "message": "User info retrieved", + }) +} + +func getUserProfile(c *gin.Context) { + loginID, _ := c.Get("loginID") + c.JSON(http.StatusOK, gin.H{ + "loginID": loginID, + "profile": "User profile data", + }) +} + +func updateUser(c *gin.Context) { + loginID, _ := c.Get("loginID") + c.JSON(http.StatusOK, gin.H{ + "loginID": loginID, + "message": "User updated", + }) +} + +func registerHandler(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"message": "Registration successful"}) +} + +func getStatus(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "ok"}) +} +``` + +### Using ProcessAuth Directly + +If you need more control, you can use `ProcessAuth` directly in your handlers: + +```go +import "github.com/click33/sa-token-go/core" + +func customHandler(c *gin.Context) { + path := c.Request.URL.Path + token := c.GetHeader("Authorization") + if token == "" { + token, _ = c.Cookie("Authorization") + } + + config := core.NewPathAuthConfig().SetInclude([]string{"/api/**"}) + result := core.ProcessAuth(path, token, config, manager) + + if result.ShouldReject() { + c.JSON(http.StatusUnauthorized, gin.H{ + "error": "path authentication required", + "path": path, + }) + c.Abort() + return + } + + // Use result.LoginID() to get the login ID + loginID := result.LoginID() + if loginID == "" { + // Token is valid but loginID not available + // You might need to get it another way + } + + // Continue with your logic + c.JSON(http.StatusOK, gin.H{"loginID": loginID}) +} +``` + +## Framework Examples + +### Gin + +```go +package main + +import ( + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/integrations/gin" + "github.com/click33/sa-token-go/storage/memory" + "github.com/gin-gonic/gin" +) + +func main() { + manager := core.NewBuilder(). + Storage(memory.NewStorage()). + Build() + + config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{"/api/public/**"}) + + plugin := gin.NewPlugin(manager) + r := gin.Default() + r.Use(plugin.PathAuthMiddleware(config)) + + r.Run(":8080") +} +``` + +### Echo + +```go +package main + +import ( + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/integrations/echo" + "github.com/click33/sa-token-go/storage/memory" + "github.com/labstack/echo/v4" +) + +func main() { + manager := core.NewBuilder(). + Storage(memory.NewStorage()). + Build() + + config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{"/api/public/**"}) + + plugin := echo.NewPlugin(manager) + e := echo.New() + e.Use(plugin.PathAuthMiddleware(config)) + + e.Start(":8080") +} +``` + +### Fiber + +```go +package main + +import ( + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/integrations/fiber" + "github.com/click33/sa-token-go/storage/memory" + "github.com/gofiber/fiber/v2" +) + +func main() { + manager := core.NewBuilder(). + Storage(memory.NewStorage()). + Build() + + config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{"/api/public/**"}) + + plugin := fiber.NewPlugin(manager) + app := fiber.New() + app.Use(plugin.PathAuthMiddleware(config)) + + app.Listen(":8080") +} +``` + +### Chi + +```go +package main + +import ( + "net/http" + + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/integrations/chi" + "github.com/click33/sa-token-go/storage/memory" + "github.com/go-chi/chi/v5" +) + +func main() { + manager := core.NewBuilder(). + Storage(memory.NewStorage()). + Build() + + config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{"/api/public/**"}) + + plugin := chi.NewPlugin(manager) + r := chi.NewRouter() + r.Use(plugin.PathAuthMiddleware(config)) + + http.ListenAndServe(":8080", r) +} +``` + +### GoFrame + +```go +package main + +import ( + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/integrations/gf" + "github.com/click33/sa-token-go/storage/memory" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + manager := core.NewBuilder(). + Storage(memory.NewStorage()). + Build() + + config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{"/api/public/**"}) + + plugin := gf.NewPlugin(manager) + s := g.Server() + s.Use(plugin.PathAuthMiddleware(config)) + + s.Run() +} +``` + +### Kratos + +```go +package main + +import ( + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/integrations/kratos" + "github.com/click33/sa-token-go/storage/memory" + "github.com/go-kratos/kratos/v2" + "github.com/go-kratos/kratos/v2/transport/http" +) + +func main() { + manager := core.NewBuilder(). + Storage(memory.NewStorage()). + Build() + + config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{"/api/public/**"}) + + plugin := kratos.NewPlugin(manager) + + httpSrv := http.NewServer( + http.Middleware( + plugin.PathAuthMiddleware(config), + ), + ) + + app := kratos.New( + kratos.Server(httpSrv), + ) + + app.Run() +} +``` + +## Error Handling + +When path authentication fails, the middleware returns a standardized error: + +```go +// Error response format +{ + "code": 401, + "message": "path authentication required", + "error": "path authentication required: this path requires authentication", + "path": "/api/user/info" // Included in context +} +``` + +You can customize error handling: + +```go +// In your error handler +if err := core.GetErrorCode(err); err == core.CodePathAuthRequired { + // Handle path authentication error + path, _ := err.GetContext("path") + // Custom error response +} +``` + +## Best Practices + +1. **Order Matters**: Place path authentication middleware before other middleware that depends on authentication +2. **Specific First**: More specific patterns should be listed before general patterns +3. **Public Paths**: Always exclude authentication endpoints (login, register) from authentication +4. **Static Files**: Exclude static file paths (CSS, JS, images) for better performance +5. **Error Handling**: Provide clear error messages to help users understand authentication requirements + +## Common Scenarios + +### Scenario 1: API with Public and Private Endpoints + +```go +config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{ + "/api/auth/**", // All auth endpoints + "/api/public/**", // Public API endpoints + }) +``` + +### Scenario 2: Admin Panel Protection + +```go +config := core.NewPathAuthConfig(). + SetInclude([]string{"/admin/**"}). + SetExclude([]string{ + "/admin/login", + "/admin/static/**", // Admin static files + }) +``` + +### Scenario 3: Multi-Tenant Application + +```go +config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{"/api/public/**"}). + SetValidator(func(loginID string) bool { + // Check tenant access + return checkTenantAccess(loginID) + }) +``` + +## API Reference + +### PathAuthConfig + +- `SetInclude(patterns []string) *PathAuthConfig` - Set paths that require authentication +- `SetExclude(patterns []string) *PathAuthConfig` - Set paths excluded from authentication +- `SetValidator(validator func(loginID string) bool) *PathAuthConfig` - Set custom login ID validator +- `Check(path string) bool` - Check if a path requires authentication + +### ProcessAuth + +```go +func ProcessAuth(path, tokenStr string, config *PathAuthConfig, mgr *Manager) *AuthResult +``` + +Processes authentication for a request path and returns an `AuthResult` containing: +- `NeedAuth bool` - Whether authentication is required +- `Token string` - The extracted token +- `TokenInfo *TokenInfo` - Token information if valid +- `IsValid bool` - Whether the token is valid + +### AuthResult + +- `ShouldReject() bool` - Check if the request should be rejected +- `LoginID() string` - Get the login ID from token info + +### Error Functions + +- `NewPathAuthRequiredError(path string) *SaTokenError` - Create path authentication required error +- `NewPathNotAllowedError(path string) *SaTokenError` - Create path not allowed error diff --git a/docs/guide/path-auth_zh.md b/docs/guide/path-auth_zh.md new file mode 100644 index 0000000000000000000000000000000000000000..807d537f6b794d4055eb6bb9e575630d1e85aad7 --- /dev/null +++ b/docs/guide/path-auth_zh.md @@ -0,0 +1,551 @@ +# 路径鉴权 + +路径鉴权允许您配置哪些路径需要鉴权,哪些路径被排除,为应用程序提供灵活的访问控制。 + +## 特性 + +- **Ant风格通配符模式** - 支持 `/**`、`/*`、`*.html` 等模式 +- **包含/排除配置** - 精细控制哪些路径需要鉴权 +- **自定义验证器** - 可选的登录ID验证函数 +- **框架集成** - 与所有支持的框架无缝协作 +- **Token提取** - 自动从请求头和Cookie中提取Token + +## 模式匹配 + +路径匹配支持Ant风格通配符: + +- `/**` - 匹配所有路径 +- `/api/**` - 匹配所有以 `/api/` 开头的路径 +- `/api/*` - 匹配 `/api/` 下的单级路径(例如 `/api/user`,但不匹配 `/api/user/profile`) +- `*.html` - 匹配以 `.html` 结尾的路径 +- `/exact` - 精确路径匹配 + +### 模式示例 + +```go +// 匹配所有路径 +"/**" + +// 匹配所有API路径 +"/api/**" + +// 匹配单级API路径 +"/api/*" + +// 匹配静态文件 +"*.html" +"*.css" +"*.js" + +// 匹配特定路径 +"/login" +"/logout" +"/public/**" +``` + +## 使用方法 + +### 基本配置 + +使用路径鉴权最简单的方式是通过中间件: + +```go +package main + +import ( + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/integrations/gin" + "github.com/click33/sa-token-go/storage/memory" +) + +func main() { + // 初始化管理器 + manager := core.NewBuilder(). + Storage(memory.NewStorage()). + TokenName("Authorization"). + Timeout(86400). + Build() + + // 创建路径鉴权配置 + config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). // 需要鉴权的路径 + SetExclude([]string{"/api/public/**"}) // 排除鉴权的路径 + + // 创建插件并使用中间件 + plugin := gin.NewPlugin(manager) + r := gin.Default() + + // 应用路径鉴权中间件 + r.Use(plugin.PathAuthMiddleware(config)) + + // 您的路由 + r.GET("/api/user/info", getUserInfo) + r.GET("/api/public/status", getStatus) // 此路径被排除 + + r.Run(":8080") +} +``` + +### 多个包含/排除模式 + +您可以指定多个模式以实现更复杂的场景: + +```go +config := core.NewPathAuthConfig(). + SetInclude([]string{ + "/api/**", // 所有API路径 + "/admin/**", // 所有管理路径 + "/user/profile", // 特定用户资料路径 + }). + SetExclude([]string{ + "/api/public/**", // 公共API路径 + "/api/auth/login", // 登录端点 + "/api/auth/register", // 注册端点 + "*.html", // 静态HTML文件 + "*.css", // CSS文件 + "*.js", // JavaScript文件 + }) +``` + +### 使用自定义验证器 + +您可以添加自定义的登录ID验证逻辑: + +```go +config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{"/api/public/**"}). + SetValidator(func(loginID string) bool { + // 自定义验证逻辑 + // 例如,检查用户是否被封禁 + if loginID == "banned_user" { + return false + } + + // 检查用户账号是否激活 + // 您可以在这里查询数据库 + // return isUserActive(loginID) + + return true + }) +``` + +### Gin完整示例 + +```go +package main + +import ( + "net/http" + + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/integrations/gin" + "github.com/click33/sa-token-go/storage/memory" + "github.com/gin-gonic/gin" +) + +func main() { + // 初始化Sa-Token管理器 + manager := core.NewBuilder(). + Storage(memory.NewStorage()). + TokenName("Authorization"). + Timeout(86400). + Build() + + // 配置路径鉴权 + pathAuthConfig := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{ + "/api/auth/login", + "/api/auth/register", + "/api/public/**", + }) + + // 创建Gin路由器 + r := gin.Default() + + // 创建插件 + plugin := gin.NewPlugin(manager) + + // 应用路径鉴权中间件 + r.Use(plugin.PathAuthMiddleware(pathAuthConfig)) + + // 公共路由(排除鉴权) + r.POST("/api/auth/login", plugin.LoginHandler) + r.POST("/api/auth/register", registerHandler) + r.GET("/api/public/status", getStatus) + + // 受保护的路由(需要鉴权) + api := r.Group("/api") + { + api.GET("/user/info", getUserInfo) + api.GET("/user/profile", getUserProfile) + api.POST("/user/update", updateUser) + } + + r.Run(":8080") +} + +func getUserInfo(c *gin.Context) { + // 从上下文获取登录ID(由PathAuthMiddleware设置) + loginID, exists := c.Get("loginID") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "loginID": loginID, + "message": "用户信息已获取", + }) +} + +func getUserProfile(c *gin.Context) { + loginID, _ := c.Get("loginID") + c.JSON(http.StatusOK, gin.H{ + "loginID": loginID, + "profile": "用户资料数据", + }) +} + +func updateUser(c *gin.Context) { + loginID, _ := c.Get("loginID") + c.JSON(http.StatusOK, gin.H{ + "loginID": loginID, + "message": "用户已更新", + }) +} + +func registerHandler(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"message": "注册成功"}) +} + +func getStatus(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "ok"}) +} +``` + +### 直接使用 ProcessAuth + +如果您需要更多控制,可以在处理器中直接使用 `ProcessAuth`: + +```go +import "github.com/click33/sa-token-go/core" + +func customHandler(c *gin.Context) { + path := c.Request.URL.Path + token := c.GetHeader("Authorization") + if token == "" { + token, _ = c.Cookie("Authorization") + } + + config := core.NewPathAuthConfig().SetInclude([]string{"/api/**"}) + result := core.ProcessAuth(path, token, config, manager) + + if result.ShouldReject() { + c.JSON(http.StatusUnauthorized, gin.H{ + "error": "路径需要鉴权", + "path": path, + }) + c.Abort() + return + } + + // 使用 result.LoginID() 获取登录ID + loginID := result.LoginID() + if loginID == "" { + // Token有效但登录ID不可用 + // 您可能需要通过其他方式获取 + } + + // 继续您的逻辑 + c.JSON(http.StatusOK, gin.H{"loginID": loginID}) +} +``` + +## 框架示例 + +### Gin + +```go +package main + +import ( + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/integrations/gin" + "github.com/click33/sa-token-go/storage/memory" + "github.com/gin-gonic/gin" +) + +func main() { + manager := core.NewBuilder(). + Storage(memory.NewStorage()). + Build() + + config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{"/api/public/**"}) + + plugin := gin.NewPlugin(manager) + r := gin.Default() + r.Use(plugin.PathAuthMiddleware(config)) + + r.Run(":8080") +} +``` + +### Echo + +```go +package main + +import ( + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/integrations/echo" + "github.com/click33/sa-token-go/storage/memory" + "github.com/labstack/echo/v4" +) + +func main() { + manager := core.NewBuilder(). + Storage(memory.NewStorage()). + Build() + + config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{"/api/public/**"}) + + plugin := echo.NewPlugin(manager) + e := echo.New() + e.Use(plugin.PathAuthMiddleware(config)) + + e.Start(":8080") +} +``` + +### Fiber + +```go +package main + +import ( + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/integrations/fiber" + "github.com/click33/sa-token-go/storage/memory" + "github.com/gofiber/fiber/v2" +) + +func main() { + manager := core.NewBuilder(). + Storage(memory.NewStorage()). + Build() + + config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{"/api/public/**"}) + + plugin := fiber.NewPlugin(manager) + app := fiber.New() + app.Use(plugin.PathAuthMiddleware(config)) + + app.Listen(":8080") +} +``` + +### Chi + +```go +package main + +import ( + "net/http" + + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/integrations/chi" + "github.com/click33/sa-token-go/storage/memory" + "github.com/go-chi/chi/v5" +) + +func main() { + manager := core.NewBuilder(). + Storage(memory.NewStorage()). + Build() + + config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{"/api/public/**"}) + + plugin := chi.NewPlugin(manager) + r := chi.NewRouter() + r.Use(plugin.PathAuthMiddleware(config)) + + http.ListenAndServe(":8080", r) +} +``` + +### GoFrame + +```go +package main + +import ( + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/integrations/gf" + "github.com/click33/sa-token-go/storage/memory" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + manager := core.NewBuilder(). + Storage(memory.NewStorage()). + Build() + + config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{"/api/public/**"}) + + plugin := gf.NewPlugin(manager) + s := g.Server() + s.Use(plugin.PathAuthMiddleware(config)) + + s.Run() +} +``` + +### Kratos + +```go +package main + +import ( + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/integrations/kratos" + "github.com/click33/sa-token-go/storage/memory" + "github.com/go-kratos/kratos/v2" + "github.com/go-kratos/kratos/v2/transport/http" +) + +func main() { + manager := core.NewBuilder(). + Storage(memory.NewStorage()). + Build() + + config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{"/api/public/**"}) + + plugin := kratos.NewPlugin(manager) + + httpSrv := http.NewServer( + http.Middleware( + plugin.PathAuthMiddleware(config), + ), + ) + + app := kratos.New( + kratos.Server(httpSrv), + ) + + app.Run() +} +``` + +## 错误处理 + +当路径鉴权失败时,中间件会返回标准化的错误: + +```go +// 错误响应格式 +{ + "code": 401, + "message": "path authentication required", + "error": "path authentication required: this path requires authentication", + "path": "/api/user/info" // 包含在上下文中 +} +``` + +您可以自定义错误处理: + +```go +// 在您的错误处理器中 +if err := core.GetErrorCode(err); err == core.CodePathAuthRequired { + // 处理路径鉴权错误 + path, _ := err.GetContext("path") + // 自定义错误响应 +} +``` + +## 最佳实践 + +1. **顺序很重要**:将路径鉴权中间件放在其他依赖认证的中间件之前 +2. **具体优先**:更具体的模式应该列在通用模式之前 +3. **公共路径**:始终将认证端点(登录、注册)排除在鉴权之外 +4. **静态文件**:排除静态文件路径(CSS、JS、图片)以提高性能 +5. **错误处理**:提供清晰的错误消息,帮助用户理解鉴权要求 + +## 常见场景 + +### 场景1:包含公共和私有端点的API + +```go +config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{ + "/api/auth/**", // 所有认证端点 + "/api/public/**", // 公共API端点 + }) +``` + +### 场景2:管理面板保护 + +```go +config := core.NewPathAuthConfig(). + SetInclude([]string{"/admin/**"}). + SetExclude([]string{ + "/admin/login", + "/admin/static/**", // 管理后台静态文件 + }) +``` + +### 场景3:多租户应用 + +```go +config := core.NewPathAuthConfig(). + SetInclude([]string{"/api/**"}). + SetExclude([]string{"/api/public/**"}). + SetValidator(func(loginID string) bool { + // 检查租户访问权限 + return checkTenantAccess(loginID) + }) +``` + +## API 参考 + +### PathAuthConfig + +- `SetInclude(patterns []string) *PathAuthConfig` - 设置需要鉴权的路径 +- `SetExclude(patterns []string) *PathAuthConfig` - 设置排除鉴权的路径 +- `SetValidator(validator func(loginID string) bool) *PathAuthConfig` - 设置自定义登录ID验证器 +- `Check(path string) bool` - 检查路径是否需要鉴权 + +### ProcessAuth + +```go +func ProcessAuth(path, tokenStr string, config *PathAuthConfig, mgr *Manager) *AuthResult +``` + +处理请求路径的鉴权,返回包含以下信息的 `AuthResult`: +- `NeedAuth bool` - 是否需要鉴权 +- `Token string` - 提取的token +- `TokenInfo *TokenInfo` - 如果有效则包含token信息 +- `IsValid bool` - token是否有效 + +### AuthResult + +- `ShouldReject() bool` - 检查请求是否应该被拒绝 +- `LoginID() string` - 从token信息中获取登录ID + +### 错误函数 + +- `NewPathAuthRequiredError(path string) *SaTokenError` - 创建路径需要鉴权错误 +- `NewPathNotAllowedError(path string) *SaTokenError` - 创建路径不允许访问错误 diff --git a/examples/annotation/annotation-example/go.mod b/examples/annotation/annotation-example/go.mod index a830f107bffc113e04ac5249196a835f45b8cc95..43cc988fbcafea5042e315b53dd869610dbdc8f1 100644 --- a/examples/annotation/annotation-example/go.mod +++ b/examples/annotation/annotation-example/go.mod @@ -3,10 +3,10 @@ module github.com/click33/sa-token-go/examples/annotation-example go 1.21 require ( - github.com/click33/sa-token-go/core v0.1.3 - github.com/click33/sa-token-go/integrations/gin v0.1.3 - github.com/click33/sa-token-go/storage/memory v0.1.3 - github.com/click33/sa-token-go/stputil v0.1.3 + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/integrations/gin v0.1.8 + github.com/click33/sa-token-go/storage/memory v0.1.8 + github.com/click33/sa-token-go/stputil v0.1.8 github.com/gin-gonic/gin v1.10.0 ) diff --git a/examples/beego/beego-example/README.md b/examples/beego/beego-example/README.md new file mode 100644 index 0000000000000000000000000000000000000000..7510e130b3d5a878fdbeafc6585eff6eb0b0895c --- /dev/null +++ b/examples/beego/beego-example/README.md @@ -0,0 +1,235 @@ +# Sa-Token Go Beego Integration Demo + +beego 框架集成 sa-token-go 认证框架的完整示例。 + +## 功能特性 + +- **登录认证**:`POST /login` - 用户登录获取 Token +- **会话管理**:`GET /session` - 查询会话状态 +- **用户信息**:`GET /user/info` - 获取用户角色和权限 +- **登出**:`POST /logout` - 登出并使 Token 失效 +- **RBAC**:`GET /admin` - 基于角色的访问控制 +- **PBAC**:`GET /data` - 基于权限的访问控制 +- **忽略认证**:`GET /health` - 忽略认证检查的端点 + +## 快速开始 + +### 1. 启动服务 + +```bash +# 使用 air 热重载(开发模式) +air + +# 或直接运行 +go run main.go + +# 或编译后运行 +go build -o server.exe main.go +./server.exe +``` + +### 2. 测试接口 + +```bash +# 1. 初始化用户权限 +curl -X POST http://localhost:8080/setup + +# 2. 登录获取 Token +curl -X POST http://localhost:8080/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin"}' + +# 3. 使用 Token 访问受保护资源 +curl http://localhost:8080/admin \ + -H "satoken: 你的token" +``` + +## API 文档 + +### 公共端点(无需认证) + +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | `/public` | 公开接口,无需认证 | +| GET | `/health` | 健康检查,忽略认证 | + +### 认证端点 + +| 方法 | 路径 | 说明 | +|------|------|------| +| POST | `/login` | 用户登录,返回 Token | +| POST | `/logout` | 用户登出,使 Token 失效 | +| GET | `/session` | 查询当前会话状态 | +| GET | `/user/info` | 获取用户信息(含角色、权限) | + +### 受保护端点 + +| 方法 | 路径 | 所需权限 | 说明 | +|------|------|---------|------| +| GET | `/admin` | admin 角色 | 管理员专用 | +| GET | `/data` | user:read 权限 | 需要读取权限 | +| GET | `/profile` | 登录状态 | 需要登录 | + +### 管理端点 + +| 方法 | 路径 | 说明 | +|------|------|------| +| POST | `/setup` | 初始化演示用户数据 | + +## 使用示例 + +### 登录请求 + +```bash +curl -X POST http://localhost:8080/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","device":"PC"}' +``` + +**响应示例:** +```json +{ + "code": 200, + "data": {"token": "d5a98ee0-f26a-4ec7-b742-d9bcab171d4b"}, + "message": "success" +} +``` + +### 访问受保护资源 + +```bash +# 带上 Token +curl http://localhost:8080/admin \ + -H "satoken: d5a98ee0-f26a-4ec7-b742-d9bcab171d4b" +``` + +**成功响应:** +```json +{"message": "welcome admin!"} +``` + +**失败响应(无权限):** +```json +{ + "code": 403, + "error": "role denied (code: 403): role denied: you don't have the required role", + "message": "role denied" +} +``` + +## Filter 中间件 + +beego 集成提供了以下 Filter 函数: + +```go +// 检查登录状态 +beego.CheckLogin() + +// 检查角色(支持多个角色,OR 逻辑) +beego.CheckRole("admin", "user") + +// 检查权限(支持多个权限,OR 逻辑) +beego.CheckPermission("user:read", "user:write") + +// 检查账号是否被封禁 +beego.CheckDisable() + +// 忽略认证检查 +beego.Ignore() +``` + +### 注册方式 + +```go +// 方式 1:使用 InsertFilter +web.InsertFilter("/admin", web.BeforeRouter, beego.CheckRole("admin")) +web.InsertFilter("/data", web.BeforeRouter, beego.CheckPermission("user:read")) + +// 方式 2:使用全局 Filter +web.GlobalFilters("", beego.CheckLogin()) +``` + +## 完整测试 + +运行完整测试套件: + +```bash +go run demo/demo.go +``` + +**测试用例:** + +``` +Test 1: Public Endpoints - 公共端点 + ✓ GET /public - 无需认证访问 + ✓ GET /health - 忽略认证 + +Test 2: Session & UserInfo - 会话与用户信息 + ✓ GET /session - 会话状态查询 + ✓ GET /user/info - 用户角色和权限 + +Test 3: RBAC - 基于角色的访问控制 + ✓ admin 用户可访问 /admin + ✓ user1 用户被拒绝访问 /admin + +Test 4: PBAC - 基于权限的访问控制 + ✓ admin 和 user1 均可访问 /data + +Test 5: Unauthorized Access - 未授权访问防护 + ✓ 无 Token 时正确拒绝 + ✓ 无效 Token 时正确拒绝 + +Test 6: Session Isolation - 会话隔离 + ✓ admin 和 user1 会话数据隔离 + +Test 7: Logout - 登出 + ✓ 登出后 Token 失效 +``` + +## 目录结构 + +``` +beego-example/ +├── main.go # 主程序入口 +├── demo/ +│ └── demo.go # 完整测试套件 +├── go.mod +└── README.md +``` + +## 注意事项 + +### 1. Setup 与 Login 的调用顺序 + +`/setup` 端点会先调用 `Login()` 创建会话,再调用 `SetRoles()/SetPermissions()` 设置权限。这是符合 sa-token 设计的方式。 + +```go +web.Post("/setup", func(ctx *context.Context) { + integrations.Login("admin") + integrations.SetRoles("admin", []string{"admin"}) + integrations.SetPermissions("admin", []string{"user:*", "admin:*"}) + // ... +}) +``` + +### 2. Token 获取方式 + +集成支持三种 Token 获取方式(按优先级): + +1. **Header**:`satoken: xxx` +2. **Authorization**:`Bearer xxx` +3. **Cookie**:`satoken=xxx` + +### 3. 错误码说明 + +| 错误码 | HTTP 状态 | 说明 | +|--------|----------|------| +| 400 | 400 | 请求参数错误 | +| 401 | 401 | 未登录或 Token 无效 | +| 403 | 403 | 角色或权限不足 | +| 500 | 500 | 服务器内部错误 | + +## 参考资料 + +- [sa-token-go 核心库](https://github.com/click33/sa-token-go) +- [beego 官方文档](https://beego.vip/) diff --git a/examples/beego/beego-example/conf/app.conf b/examples/beego/beego-example/conf/app.conf new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/examples/beego/beego-example/demo/demo.go b/examples/beego/beego-example/demo/demo.go new file mode 100644 index 0000000000000000000000000000000000000000..a2ce41139c8deacd760fccd1302627a9737c0910 --- /dev/null +++ b/examples/beego/beego-example/demo/demo.go @@ -0,0 +1,415 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" +) + +const baseURL = "http://localhost:8080" + +type Response struct { + Code int `json:"code"` + Message string `json:"message"` + Data json.RawMessage `json:"data,omitempty"` + Error string `json:"error,omitempty"` +} + +type LoginResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Data struct { + Token string `json:"token"` + } `json:"data"` +} + +func main() { + fmt.Println() + fmt.Println("╔══════════════════════════════════════════════════════════════════╗") + fmt.Println("║ Sa-Token Go Beego Integration - Comprehensive Test Suite ║") + fmt.Println("╚══════════════════════════════════════════════════════════════════╝") + fmt.Println() + + client := &http.Client{} + + // ============================================================ + // Step 0: Setup - Login users and configure roles/permissions + // /setup does: Login() -> SetRoles() -> SetPermissions() + // This ensures session has both login info and roles/permissions + // ============================================================ + fmt.Println("[Step 0] Setup: Login users + configure roles & permissions") + fmt.Println(" └─ /setup: Login() -> SetRoles() -> SetPermissions()") + + resp, err := http.Post(baseURL+"/setup", "application/json", nil) + if err != nil { + fmt.Printf(" [FAIL] Setup failed: %v\n", err) + return + } + body, _ := io.ReadAll(resp.Body) + resp.Body.Close() + if strings.Contains(string(body), "configured") { + fmt.Println(" [OK] Demo users configured:") + fmt.Println(" admin: roles=[admin], perms=[user:*, admin:*]") + fmt.Println(" user1: roles=[user], perms=[user:read, user:write]") + } + + // Get admin token via /login (roles already set in setup via Login+SetRoles) + fmt.Print("\n[Step 1] Get admin token ... ") + resp, err = http.Post(baseURL+"/login", "application/json", bytes.NewBufferString(`{"username":"admin"}`)) + if err != nil { + fmt.Printf(" [FAIL] %v\n", err) + return + } + body, _ = io.ReadAll(resp.Body) + resp.Body.Close() + var adminLogin LoginResponse + json.Unmarshal(body, &adminLogin) + if adminLogin.Code != 200 { + fmt.Printf(" [FAIL] login failed: %s\n", string(body)) + return + } + adminToken := adminLogin.Data.Token + fmt.Printf("OK (token: %s)\n", adminToken) + + // Get user1 token + fmt.Print("[Step 2] Get user1 token ... ") + resp, err = http.Post(baseURL+"/login", "application/json", bytes.NewBufferString(`{"username":"user1"}`)) + if err != nil { + fmt.Printf(" [FAIL] %v\n", err) + return + } + body, _ = io.ReadAll(resp.Body) + resp.Body.Close() + var user1Login LoginResponse + json.Unmarshal(body, &user1Login) + user1Token := user1Login.Data.Token + fmt.Printf("OK (token: %s)\n", user1Token) + + // ============================================================ + // Test 1: Public Endpoints + // ============================================================ + fmt.Println() + fmt.Println("╔══════════════════════════════════════════════════════════════════╗") + fmt.Println("║ Test 1: Public Endpoints (No Auth Required) ║") + fmt.Println("╚══════════════════════════════════════════════════════════════════╝") + + fmt.Print(" GET /public ... ") + resp, err = http.Get(baseURL + "/public") + if err != nil { + fmt.Printf("FAIL: %v\n", err) + return + } + body, _ = io.ReadAll(resp.Body) + resp.Body.Close() + if strings.Contains(string(body), "public endpoint") { + fmt.Println("OK - Public endpoint accessible without token") + } else { + fmt.Printf("FAIL: %s\n", string(body)) + } + + fmt.Print(" GET /health (with fake token - should ignore auth) ... ") + req, _ := http.NewRequest("GET", baseURL+"/health", nil) + req.Header.Set("satoken", "fake-token-ignored") + resp, err = client.Do(req) + if err != nil { + fmt.Printf("FAIL: %v\n", err) + return + } + body, _ = io.ReadAll(resp.Body) + resp.Body.Close() + if strings.Contains(string(body), "ok") { + fmt.Println("OK - Health endpoint ignores auth (Ignore filter)") + } else { + fmt.Printf("FAIL: %s\n", string(body)) + } + + // ============================================================ + // Test 2: Session & UserInfo with Roles/Permissions + // ============================================================ + fmt.Println() + fmt.Println("╔══════════════════════════════════════════════════════════════════╗") + fmt.Println("║ Test 2: Session & UserInfo (Roles & Permissions) ║") + fmt.Println("╚══════════════════════════════════════════════════════════════════╝") + + fmt.Print(" GET /session (admin token) ... ") + req, _ = http.NewRequest("GET", baseURL+"/session", nil) + req.Header.Set("satoken", adminToken) + resp, err = client.Do(req) + if err != nil { + fmt.Printf("FAIL: %v\n", err) + return + } + body, _ = io.ReadAll(resp.Body) + resp.Body.Close() + if strings.Contains(string(body), `"isLogin":true`) && strings.Contains(string(body), `"loginId":"admin"`) { + fmt.Println("OK - Admin session valid") + } else { + fmt.Printf("FAIL: %s\n", string(body)) + } + + fmt.Print(" GET /user/info (admin) - check roles & permissions ... ") + req, _ = http.NewRequest("GET", baseURL+"/user/info", nil) + req.Header.Set("satoken", adminToken) + resp, err = client.Do(req) + if err != nil { + fmt.Printf("FAIL: %v\n", err) + return + } + body, _ = io.ReadAll(resp.Body) + resp.Body.Close() + if strings.Contains(string(body), `"loginId":"admin"`) { + var r Response + json.Unmarshal(body, &r) + var data map[string]interface{} + json.Unmarshal(r.Data, &data) + fmt.Println("OK - UserInfo retrieved") + if roles, ok := data["roles"].([]interface{}); ok { + fmt.Printf(" Roles: %v\n", roles) + } + if perms, ok := data["permissions"].([]interface{}); ok { + fmt.Printf(" Permissions: %v\n", perms) + } + } else { + fmt.Printf("FAIL: %s\n", string(body)) + } + + // ============================================================ + // Test 3: RBAC - Role-Based Access Control + // ============================================================ + fmt.Println() + fmt.Println("╔══════════════════════════════════════════════════════════════════╗") + fmt.Println("║ Test 3: Role-Based Access Control (RBAC) ║") + fmt.Println("╚══════════════════════════════════════════════════════════════════╝") + + fmt.Print(" GET /admin (admin token - has admin role) ... ") + req, _ = http.NewRequest("GET", baseURL+"/admin", nil) + req.Header.Set("satoken", adminToken) + resp, err = client.Do(req) + if err != nil { + fmt.Printf("FAIL: %v\n", err) + return + } + body, _ = io.ReadAll(resp.Body) + resp.Body.Close() + if strings.Contains(string(body), "welcome admin") { + fmt.Println("OK - Admin access granted (admin role verified)") + } else { + fmt.Printf("FAIL: %s\n", string(body)) + } + + fmt.Print(" GET /admin (user1 token - no admin role) ... ") + req, _ = http.NewRequest("GET", baseURL+"/admin", nil) + req.Header.Set("satoken", user1Token) + resp, err = client.Do(req) + if err != nil { + fmt.Printf("FAIL: %v\n", err) + return + } + body, _ = io.ReadAll(resp.Body) + resp.Body.Close() + if strings.Contains(string(body), "403") && strings.Contains(string(body), "role denied") { + fmt.Println("OK - Access denied (user1 lacks admin role)") + } else { + fmt.Printf("FAIL: expected 403, got: %s\n", string(body)) + } + + // ============================================================ + // Test 4: PBAC - Permission-Based Access Control + // ============================================================ + fmt.Println() + fmt.Println("╔══════════════════════════════════════════════════════════════════╗") + fmt.Println("║ Test 4: Permission-Based Access Control (PBAC) ║") + fmt.Println("╚══════════════════════════════════════════════════════════════════╝") + + fmt.Print(" GET /data (admin token - has user:read perm) ... ") + req, _ = http.NewRequest("GET", baseURL+"/data", nil) + req.Header.Set("satoken", adminToken) + resp, err = client.Do(req) + if err != nil { + fmt.Printf("FAIL: %v\n", err) + return + } + body, _ = io.ReadAll(resp.Body) + resp.Body.Close() + if strings.Contains(string(body), "item1") && strings.Contains(string(body), "item2") { + fmt.Println("OK - Admin can access /data (user:read permission)") + } else { + fmt.Printf("FAIL: %s\n", string(body)) + } + + fmt.Print(" GET /data (user1 token - has user:read perm) ... ") + req, _ = http.NewRequest("GET", baseURL+"/data", nil) + req.Header.Set("satoken", user1Token) + resp, err = client.Do(req) + if err != nil { + fmt.Printf("FAIL: %v\n", err) + return + } + body, _ = io.ReadAll(resp.Body) + resp.Body.Close() + if strings.Contains(string(body), "item1") { + fmt.Println("OK - user1 can access /data (user:read permission)") + } else { + fmt.Printf("FAIL: %s\n", string(body)) + } + + // ============================================================ + // Test 5: Unauthorized Access Prevention + // ============================================================ + fmt.Println() + fmt.Println("╔══════════════════════════════════════════════════════════════════╗") + fmt.Println("║ Test 5: Unauthorized Access Prevention ║") + fmt.Println("╚══════════════════════════════════════════════════════════════════╝") + + fmt.Print(" GET /profile (no token) ... ") + req, _ = http.NewRequest("GET", baseURL+"/profile", nil) + resp, err = client.Do(req) + if err != nil { + fmt.Printf("FAIL: %v\n", err) + return + } + body, _ = io.ReadAll(resp.Body) + resp.Body.Close() + if strings.Contains(string(body), "401") || strings.Contains(string(body), "not login") { + fmt.Println("OK - Rejected: no token provided") + } else { + fmt.Printf("FAIL: expected 401, got: %s\n", string(body)) + } + + fmt.Print(" GET /profile (invalid token) ... ") + req, _ = http.NewRequest("GET", baseURL+"/profile", nil) + req.Header.Set("satoken", "invalid-token-xyz") + resp, err = client.Do(req) + if err != nil { + fmt.Printf("FAIL: %v\n", err) + return + } + body, _ = io.ReadAll(resp.Body) + resp.Body.Close() + if strings.Contains(string(body), "401") || strings.Contains(string(body), "not login") { + fmt.Println("OK - Rejected: invalid token") + } else { + fmt.Printf("FAIL: expected 401, got: %s\n", string(body)) + } + + // ============================================================ + // Test 6: Session Isolation + // ============================================================ + fmt.Println() + fmt.Println("╔══════════════════════════════════════════════════════════════════╗") + fmt.Println("║ Test 6: Session Isolation ║") + fmt.Println("╚══════════════════════════════════════════════════════════════════╝") + + fmt.Print(" GET /user/info (admin token) - verify admin's data ... ") + req, _ = http.NewRequest("GET", baseURL+"/user/info", nil) + req.Header.Set("satoken", adminToken) + resp, err = client.Do(req) + if err != nil { + fmt.Printf("FAIL: %v\n", err) + return + } + body, _ = io.ReadAll(resp.Body) + resp.Body.Close() + if strings.Contains(string(body), `"loginId":"admin"`) { + fmt.Println("OK - Admin session isolated correctly") + } else { + fmt.Printf("FAIL: %s\n", string(body)) + } + + fmt.Print(" GET /user/info (user1 token) - verify user1's data ... ") + req, _ = http.NewRequest("GET", baseURL+"/user/info", nil) + req.Header.Set("satoken", user1Token) + resp, err = client.Do(req) + if err != nil { + fmt.Printf("FAIL: %v\n", err) + return + } + body, _ = io.ReadAll(resp.Body) + resp.Body.Close() + if strings.Contains(string(body), `"loginId":"user1"`) { + fmt.Println("OK - user1 session isolated correctly") + } else { + fmt.Printf("FAIL: %s\n", string(body)) + } + + // ============================================================ + // Test 7: Logout + // ============================================================ + fmt.Println() + fmt.Println("╔══════════════════════════════════════════════════════════════════╗") + fmt.Println("║ Test 7: Logout & Token Invalidation ║") + fmt.Println("╚══════════════════════════════════════════════════════════════════╝") + + fmt.Print(" POST /logout (admin token) ... ") + req, _ = http.NewRequest("POST", baseURL+"/logout", nil) + req.Header.Set("satoken", adminToken) + resp, err = client.Do(req) + if err != nil { + fmt.Printf("FAIL: %v\n", err) + return + } + body, _ = io.ReadAll(resp.Body) + resp.Body.Close() + if strings.Contains(string(body), "logout successful") { + fmt.Println("OK - Logout successful") + } else { + fmt.Printf("FAIL: %s\n", string(body)) + } + + fmt.Print(" GET /user/info (admin token after logout) ... ") + req, _ = http.NewRequest("GET", baseURL+"/user/info", nil) + req.Header.Set("satoken", adminToken) + resp, err = client.Do(req) + if err != nil { + fmt.Printf("FAIL: %v\n", err) + return + } + body, _ = io.ReadAll(resp.Body) + resp.Body.Close() + if strings.Contains(string(body), "401") || strings.Contains(string(body), "not login") { + fmt.Println("OK - Token invalidated after logout") + } else { + fmt.Printf("FAIL: expected 401, got: %s\n", string(body)) + } + + // user1 token should still work + fmt.Print(" GET /user/info (user1 token - should still work) ... ") + req, _ = http.NewRequest("GET", baseURL+"/user/info", nil) + req.Header.Set("satoken", user1Token) + resp, err = client.Do(req) + if err != nil { + fmt.Printf("FAIL: %v\n", err) + return + } + body, _ = io.ReadAll(resp.Body) + resp.Body.Close() + if strings.Contains(string(body), `"loginId":"user1"`) { + fmt.Println("OK - user1 token still valid (logout is user-specific)") + } else { + fmt.Printf("FAIL: %s\n", string(body)) + } + + // ============================================================ + // Summary + // ============================================================ + fmt.Println() + fmt.Println("╔══════════════════════════════════════════════════════════════════╗") + fmt.Println("║ Test Summary ║") + fmt.Println("╠══════════════════════════════════════════════════════════════════╣") + fmt.Println("║ ✓ Test 1: Public endpoints - no auth required ║") + fmt.Println("║ ✓ Test 2: Health endpoint - ignores auth ║") + fmt.Println("║ ✓ Test 3: Session & UserInfo with roles & permissions ║") + fmt.Println("║ ✓ Test 4: RBAC - Role-based access control ║") + fmt.Println("║ ✓ Test 5: PBAC - Permission-based access control ║") + fmt.Println("║ ✓ Test 6: Unauthorized access properly rejected ║") + fmt.Println("║ ✓ Test 7: Session isolation between users ║") + fmt.Println("║ ✓ Test 8: Logout invalidates token ║") + fmt.Println("╚══════════════════════════════════════════════════════════════════╝") + fmt.Println() + + time.Sleep(100 * time.Millisecond) +} diff --git a/examples/beego/beego-example/go.mod b/examples/beego/beego-example/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..c4a32799b3628af404a370e0515cea7db4d56717 --- /dev/null +++ b/examples/beego/beego-example/go.mod @@ -0,0 +1,42 @@ +module github.com/click33/sa-token-go/examples/beego/beego-example + +go 1.24.4 + +replace ( + github.com/click33/sa-token-go/core => ../../../core + github.com/click33/sa-token-go/integrations/beego => ../../../integrations/beego + github.com/click33/sa-token-go/storage/memory => ../../../storage/memory + github.com/click33/sa-token-go/stputil => ../../../stputil +) + +require ( + github.com/beego/beego/v2 v2.3.10 + github.com/click33/sa-token-go/integrations/beego v0.0.0-00010101000000-000000000000 + github.com/click33/sa-token-go/storage/memory v0.0.0-00010101000000-000000000000 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/click33/sa-token-go/core v0.1.8 // indirect + github.com/click33/sa-token-go/stputil v0.1.8 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/panjf2000/ants/v2 v2.11.3 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.63.0 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02 // indirect + golang.org/x/crypto v0.38.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/examples/beego/beego-example/go.sum b/examples/beego/beego-example/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..ef6a7c8a3f80c9e55e0615296ee98ed52c69a56a --- /dev/null +++ b/examples/beego/beego-example/go.sum @@ -0,0 +1,66 @@ +github.com/beego/beego/v2 v2.3.10 h1:53us+Lzc/bwFwjyRi62+FKFEcWiIbmKbhW4/FQ3aNEw= +github.com/beego/beego/v2 v2.3.10/go.mod h1:IY3bkfRge4Yj2XhgdMuvznM4HK/ovxQLOHJxAJ+QRgo= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw= +github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg= +github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= +github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02 h1:v9ezJDHA1XGxViAUSIoO/Id7Fl63u6d0YmsAm+/p2hs= +github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02/go.mod h1:RF16/A3L0xSa0oSERcnhd8Pu3IXSDZSK2gmGIMsttFE= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/beego/beego-example/main.go b/examples/beego/beego-example/main.go new file mode 100644 index 0000000000000000000000000000000000000000..d2a9c6feb7f8e2ddc151cac25f9495436c919bab --- /dev/null +++ b/examples/beego/beego-example/main.go @@ -0,0 +1,135 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/beego/beego/v2/server/web" + "github.com/beego/beego/v2/server/web/context" + integrations "github.com/click33/sa-token-go/integrations/beego" + memory "github.com/click33/sa-token-go/storage/memory" +) + +func main() { + // Initialize sa-token storage and manager + storage := memory.NewStorage() + manager := integrations.NewManager(storage, integrations.DefaultConfig()) + integrations.SetManager(manager) + + // Create sa-token plugin for handlers + plugin := integrations.NewPlugin(manager) + + // Public endpoints - no auth required + web.Get("/public", func(ctx *context.Context) { + ctx.Output.Body([]byte(`{"message": "public endpoint, no auth required"}`)) + }) + + // Login endpoint + web.Post("/login", plugin.LoginHandler) + + // Logout endpoint + web.Post("/logout", plugin.LogoutHandler) + + // User info endpoint - requires login + web.Get("/user/info", plugin.UserInfoHandler) + + // Admin endpoint - requires "admin" role + web.Get("/admin", func(ctx *context.Context) { + ctx.Output.Body([]byte(`{"message": "welcome admin!"}`)) + }) + + // Permission endpoint - requires "user:read" permission + web.Get("/data", func(ctx *context.Context) { + ctx.Output.Body([]byte(`{"data": ["item1", "item2", "item3"]}`)) + }) + + // Profile endpoint - requires login + web.Get("/profile", func(ctx *context.Context) { + ctx.Output.Body([]byte(`{"message": "this is your profile"}`)) + }) + + // Health endpoint - bypasses auth + web.Get("/health", func(ctx *context.Context) { + ctx.Output.Body([]byte(`{"status": "ok"}`)) + }) + + // Set permissions for a user (demo) + web.Post("/setup", func(ctx *context.Context) { + // Login first (creates session), then set roles/permissions + // This ensures roles persist after login + integrations.Login("admin") + integrations.SetRoles("admin", []string{"admin"}) + integrations.SetPermissions("admin", []string{"user:*", "admin:*"}) + + integrations.Login("user1") + integrations.SetRoles("user1", []string{"user"}) + integrations.SetPermissions("user1", []string{"user:read", "user:write"}) + + ctx.Output.Body([]byte(`{"message": "demo users configured"}`)) + }) + + // Get session info + web.Get("/session", func(ctx *context.Context) { + token := ctx.Input.Header("satoken") + if token == "" { + token = ctx.Input.Cookie("satoken") + } + + if token == "" { + ctx.Output.Body([]byte(`{"error": "no token"}`)) + return + } + + isLogin := integrations.IsLogin(token) + loginID, _ := integrations.GetLoginID(token) + + result := map[string]interface{}{ + "isLogin": isLogin, + "loginId": loginID, + } + + data, _ := json.Marshal(result) + ctx.Output.Body(data) + }) + + // ========== Register filters for protected routes ========== + + // /user/info requires login + web.InsertFilter("/user/info", web.BeforeRouter, integrations.CheckLogin()) + + // /admin requires admin role + web.InsertFilter("/admin", web.BeforeRouter, integrations.CheckRole("admin")) + + // /data requires user:read permission + web.InsertFilter("/data", web.BeforeRouter, integrations.CheckPermission("user:read")) + + // /profile requires login + web.InsertFilter("/profile", web.BeforeRouter, integrations.CheckLogin()) + + // /logout requires login + web.InsertFilter("/logout", web.BeforeRouter, integrations.CheckLogin()) + + // /session requires login + web.InsertFilter("/session", web.BeforeRouter, integrations.CheckLogin()) + + // /health bypasses auth (ignore) + web.InsertFilter("/health", web.BeforeRouter, integrations.Ignore()) + + // Run server + fmt.Println("Server starting on :8080") + fmt.Println("Endpoints:") + fmt.Println(" GET /public - Public (no auth)") + fmt.Println(" POST /login - Login (public)") + fmt.Println(" POST /logout - Logout (requires login)") + fmt.Println(" GET /user/info - User info (requires login)") + fmt.Println(" GET /admin - Admin only (requires admin role)") + fmt.Println(" GET /data - Data (requires user:read permission)") + fmt.Println(" GET /profile - Profile (requires login)") + fmt.Println(" GET /health - Health check (ignores auth)") + fmt.Println(" POST /setup - Setup demo users") + fmt.Println(" GET /session - Check session (requires login)") + + log.Println("Server starting...") + web.Run() +} diff --git a/examples/chi/chi-example/go.mod b/examples/chi/chi-example/go.mod index 509b792240214d99692853a2abce89c7f9c1b7fb..e287429f2fe4dee695d5480cae42b516f6e24a10 100644 --- a/examples/chi/chi-example/go.mod +++ b/examples/chi/chi-example/go.mod @@ -3,9 +3,9 @@ module github.com/click33/sa-token-go/examples/chi-example go 1.21 require ( - github.com/click33/sa-token-go/core v0.1.3 - github.com/click33/sa-token-go/integrations/chi v0.1.3 - github.com/click33/sa-token-go/storage/memory v0.1.3 + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/integrations/chi v0.1.8 + github.com/click33/sa-token-go/storage/memory v0.1.8 github.com/go-chi/chi/v5 v5.0.11 ) diff --git a/examples/echo/echo-example/go.mod b/examples/echo/echo-example/go.mod index b08a0a4985dd5ac9da988e6c0126b4d786ee5a42..e191694ddb9578d80658404629debee73f78385a 100644 --- a/examples/echo/echo-example/go.mod +++ b/examples/echo/echo-example/go.mod @@ -5,14 +5,14 @@ go 1.23.0 toolchain go1.24.1 require ( - github.com/click33/sa-token-go/core v0.1.3 - github.com/click33/sa-token-go/integrations/echo v0.1.3 - github.com/click33/sa-token-go/storage/memory v0.1.3 + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/integrations/echo v0.1.8 + github.com/click33/sa-token-go/storage/memory v0.1.8 github.com/labstack/echo/v4 v4.11.4 ) require ( - github.com/click33/sa-token-go/stputil v0.0.0-20251017234446-3cf2bdee68cc // indirect + github.com/click33/sa-token-go/stputil v0.1.8 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/google/uuid v1.6.0 // indirect diff --git a/examples/fiber/fiber-example/go.mod b/examples/fiber/fiber-example/go.mod index e87d400aa5eb41a883b5f7e12ab3cc9bcd3c1eca..bfe6cbdb419758e9e1cdd3e7a87d764338774c00 100644 --- a/examples/fiber/fiber-example/go.mod +++ b/examples/fiber/fiber-example/go.mod @@ -3,9 +3,9 @@ module github.com/click33/sa-token-go/examples/fiber-example go 1.21 require ( - github.com/click33/sa-token-go/core v0.1.3 - github.com/click33/sa-token-go/integrations/fiber v0.1.3 - github.com/click33/sa-token-go/storage/memory v0.1.3 + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/integrations/fiber v0.1.8 + github.com/click33/sa-token-go/storage/memory v0.1.8 github.com/gofiber/fiber/v2 v2.52.0 ) diff --git a/examples/gf/go.mod b/examples/gf/go.mod index fdf155ee73eeae8088f62a0a5929ac8391fb8bb9..35881d049e9283e7ddc40a1461f7bcdcd3aa8f32 100644 --- a/examples/gf/go.mod +++ b/examples/gf/go.mod @@ -9,16 +9,16 @@ replace ( ) require ( - github.com/click33/sa-token-go/integrations/gf v0.0.0-00010101000000-000000000000 - github.com/click33/sa-token-go/storage/memory v0.0.0-00010101000000-000000000000 + github.com/click33/sa-token-go/integrations/gf v0.1.8 + github.com/click33/sa-token-go/storage/memory v0.1.8 github.com/gogf/gf/v2 v2.9.4 ) require ( github.com/BurntSushi/toml v1.5.0 // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect - github.com/click33/sa-token-go/core v0.1.3 // indirect - github.com/click33/sa-token-go/stputil v0.1.3 // indirect + github.com/click33/sa-token-go/core v0.1.8 // indirect + github.com/click33/sa-token-go/stputil v0.1.8 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect diff --git a/examples/gin/gin-example/go.mod b/examples/gin/gin-example/go.mod index 939c5294e467d32d2bfbd7fe9be84d197e031960..22329e2a83907315b16ed5a2a8b299e552dcb575 100644 --- a/examples/gin/gin-example/go.mod +++ b/examples/gin/gin-example/go.mod @@ -5,16 +5,16 @@ go 1.23.0 toolchain go1.24.1 require ( - github.com/click33/sa-token-go/integrations/gin v0.1.3 - github.com/click33/sa-token-go/storage/memory v0.1.3 + github.com/click33/sa-token-go/integrations/gin v0.1.8 + github.com/click33/sa-token-go/storage/memory v0.1.8 github.com/gin-gonic/gin v1.10.0 github.com/spf13/viper v1.18.2 ) require ( github.com/bytedance/sonic v1.11.6 // indirect - github.com/click33/sa-token-go/core v0.1.3 // indirect - github.com/click33/sa-token-go/stputil v0.1.3 // indirect + github.com/click33/sa-token-go/core v0.1.8 // indirect + github.com/click33/sa-token-go/stputil v0.1.8 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect diff --git a/examples/gin/gin-example/go.sum b/examples/gin/gin-example/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..9a20c5af5d3773fba85424bb0814d51a0f5237e8 --- /dev/null +++ b/examples/gin/gin-example/go.sum @@ -0,0 +1,141 @@ +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/click33/sa-token-go/stputil v0.1.7 h1:omAPMerECe8gBRFHLzjxnuNYFPipcHi/gd3U75r4gzg= +github.com/click33/sa-token-go/stputil v0.1.7/go.mod h1:YY4NzfwVMwPUQLDBk9C5eVLQ08oI3vNSFQhBuZBPtgY= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg= +github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/examples/gin/gin-simple/go.mod b/examples/gin/gin-simple/go.mod index e9dd2a9ec0a87d58af47fa551afa7df2bef71167..577337876a0947f55b738b2b8f2e294959d8f51e 100644 --- a/examples/gin/gin-simple/go.mod +++ b/examples/gin/gin-simple/go.mod @@ -5,15 +5,15 @@ go 1.23.0 toolchain go1.24.1 require ( - github.com/click33/sa-token-go/integrations/gin v0.1.3 - github.com/click33/sa-token-go/storage/memory v0.1.3 + github.com/click33/sa-token-go/integrations/gin v0.1.8 + github.com/click33/sa-token-go/storage/memory v0.1.8 github.com/gin-gonic/gin v1.10.0 ) require ( github.com/bytedance/sonic v1.11.6 // indirect - github.com/click33/sa-token-go/core v0.1.3 // indirect - github.com/click33/sa-token-go/stputil v0.1.3 // indirect + github.com/click33/sa-token-go/core v0.1.8 // indirect + github.com/click33/sa-token-go/stputil v0.1.8 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect diff --git a/examples/hertz/herz-example/.gitignore b/examples/hertz/herz-example/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..101ea87cf8e19cb711af66d19a3c5e963abfdbf3 --- /dev/null +++ b/examples/hertz/herz-example/.gitignore @@ -0,0 +1,37 @@ +*.o +*.a +*.so +_obj +_test +*.[568vq] +[568vq].out +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* +_testmain.go +*.exe +*.exe~ +*.test +*.prof +*.rar +*.zip +*.gz +*.psd +*.bmd +*.cfg +*.pptx +*.log +*nohup.out +*settings.pyc +*.sublime-project +*.sublime-workspace +!.gitkeep +.DS_Store +/.idea +/.vscode +/output +*.local.yml +dumped_hertz_remote_config.json + \ No newline at end of file diff --git a/examples/hertz/herz-example/.hz b/examples/hertz/herz-example/.hz new file mode 100644 index 0000000000000000000000000000000000000000..791041dc51d93d11a2b73652efa2447f2e982bb0 --- /dev/null +++ b/examples/hertz/herz-example/.hz @@ -0,0 +1,6 @@ +// Code generated by hz. DO NOT EDIT. + +hz version: v0.9.7 +handlerDir: "" +modelDir: "" +routerDir: "" diff --git a/examples/hertz/herz-example/biz/handler/user/user_service.go b/examples/hertz/herz-example/biz/handler/user/user_service.go new file mode 100644 index 0000000000000000000000000000000000000000..53e3d0977788518d13ed7db6aca7f46f23a7b86d --- /dev/null +++ b/examples/hertz/herz-example/biz/handler/user/user_service.go @@ -0,0 +1,131 @@ +// Code generated by hertz generator. + +package user + +import ( + "context" + "time" + + user "github.com/click33/sa-token-go/examples/hertz/herz-example/biz/model/user" + "github.com/click33/sa-token-go/stputil" + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/protocol/consts" +) + +// Login . +// @router /login [POST] +func Login(ctx context.Context, c *app.RequestContext) { + var err error + var req user.LoginReq + err = c.BindAndValidate(&req) + if err != nil { + c.String(consts.StatusBadRequest, err.Error()) + return + } + + token, err := stputil.Login(req.GetUserId()) + if err != nil { + c.String(consts.StatusBadRequest, err.Error()) + return + } + loginID, err := stputil.GetLoginID(token) + if err != nil { + c.String(consts.StatusBadRequest, err.Error()) + return + } + err = stputil.SetRoles(loginID, []string{"manager"}) + if err != nil { + c.String(consts.StatusBadRequest, err.Error()) + return + } + + resp := new(user.LoginResp) + resp.Token = token + + c.JSON(consts.StatusOK, resp) +} + +// Public . +// @router /public [GET] +func Public(ctx context.Context, c *app.RequestContext) { + + resp := new(user.MessageResp) + resp.Message = "public" + + c.JSON(consts.StatusOK, resp) +} + +// UserInfo . +// @router /user [GET] +func UserInfo(ctx context.Context, c *app.RequestContext) { + + resp := new(user.UserInfoResp) + loginID, err := stputil.GetLoginID(c.Request.Header.Get("satoken")) + if err != nil { + c.String(consts.StatusBadRequest, err.Error()) + return + } + roles, _ := stputil.GetRoles(loginID) + permissions, _ := stputil.GetPermissions(loginID) + + resp.LoginId = loginID + resp.Roles = roles + resp.Permissions = permissions + + c.JSON(consts.StatusOK, resp) +} + +// Admin . +// @router /admin [GET] +func Admin(ctx context.Context, c *app.RequestContext) { + + resp := new(user.MessageResp) + resp.Message = "admin" + + c.JSON(consts.StatusOK, resp) +} + +// Manager . +// @router /manager [GET] +func Manager(ctx context.Context, c *app.RequestContext) { + + resp := new(user.MessageResp) + resp.Message = "manager" + + c.JSON(consts.StatusOK, resp) +} + +// Sensitive . +// @router /sensitive [GET] +func Sensitive(ctx context.Context, c *app.RequestContext) { + + resp := new(user.SensitiveResp) + loginID, err := stputil.GetLoginID(c.Request.Header.Get("satoken")) + if err != nil { + c.String(consts.StatusBadRequest, err.Error()) + return + } + resp.Sensitive = stputil.IsDisable(loginID) + + c.JSON(consts.StatusOK, resp) +} + +// Disable . +// @router /disable [GET] +func Disable(ctx context.Context, c *app.RequestContext) { + var err error + + loginID, err := stputil.GetLoginID(c.Request.Header.Get("satoken")) + if err != nil { + c.String(consts.StatusBadRequest, err.Error()) + return + } + err = stputil.Disable(loginID, time.Hour) + if err != nil { + c.String(consts.StatusBadRequest, err.Error()) + return + } + resp := new(user.MessageResp) + + c.JSON(consts.StatusOK, resp) +} diff --git a/examples/hertz/herz-example/biz/model/user/user.go b/examples/hertz/herz-example/biz/model/user/user.go new file mode 100644 index 0000000000000000000000000000000000000000..9cb30f1d41fa87e5d1971d9a94551c4589811597 --- /dev/null +++ b/examples/hertz/herz-example/biz/model/user/user.go @@ -0,0 +1,3004 @@ +// Code generated by thriftgo (0.4.3). DO NOT EDIT. + +package user + +import ( + "context" + "fmt" + "github.com/apache/thrift/lib/go/thrift" +) + +type LoginReq struct { + UserId string `thrift:"userId,1" json:"userId" query:"userId"` +} + +func NewLoginReq() *LoginReq { + return &LoginReq{} +} + +func (p *LoginReq) InitDefault() { +} + +func (p *LoginReq) GetUserId() (v string) { + return p.UserId +} + +var fieldIDToName_LoginReq = map[int16]string{ + 1: "userId", +} + +func (p *LoginReq) Read(iprot thrift.TProtocol) (err error) { + + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRING { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_LoginReq[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *LoginReq) ReadField1(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.UserId = _field + return nil +} + +func (p *LoginReq) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("LoginReq"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *LoginReq) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("userId", thrift.STRING, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.UserId); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} + +func (p *LoginReq) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("LoginReq(%+v)", *p) + +} + +type LoginResp struct { + Token string `thrift:"token,1" form:"token" json:"token" query:"token"` +} + +func NewLoginResp() *LoginResp { + return &LoginResp{} +} + +func (p *LoginResp) InitDefault() { +} + +func (p *LoginResp) GetToken() (v string) { + return p.Token +} + +var fieldIDToName_LoginResp = map[int16]string{ + 1: "token", +} + +func (p *LoginResp) Read(iprot thrift.TProtocol) (err error) { + + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRING { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_LoginResp[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *LoginResp) ReadField1(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.Token = _field + return nil +} + +func (p *LoginResp) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("LoginResp"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *LoginResp) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("token", thrift.STRING, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.Token); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} + +func (p *LoginResp) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("LoginResp(%+v)", *p) + +} + +type MessageResp struct { + Message string `thrift:"message,1" form:"message" json:"message" query:"message"` +} + +func NewMessageResp() *MessageResp { + return &MessageResp{} +} + +func (p *MessageResp) InitDefault() { +} + +func (p *MessageResp) GetMessage() (v string) { + return p.Message +} + +var fieldIDToName_MessageResp = map[int16]string{ + 1: "message", +} + +func (p *MessageResp) Read(iprot thrift.TProtocol) (err error) { + + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRING { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_MessageResp[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *MessageResp) ReadField1(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.Message = _field + return nil +} + +func (p *MessageResp) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("MessageResp"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *MessageResp) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("message", thrift.STRING, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.Message); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} + +func (p *MessageResp) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("MessageResp(%+v)", *p) + +} + +type UserInfoResp struct { + LoginId string `thrift:"loginId,1" form:"loginId" json:"loginId" query:"loginId"` + Roles []string `thrift:"roles,2,default,list" form:"roles" json:"roles" query:"roles"` + Permissions []string `thrift:"permissions,3,default,list" form:"permissions" json:"permissions" query:"permissions"` +} + +func NewUserInfoResp() *UserInfoResp { + return &UserInfoResp{} +} + +func (p *UserInfoResp) InitDefault() { +} + +func (p *UserInfoResp) GetLoginId() (v string) { + return p.LoginId +} + +func (p *UserInfoResp) GetRoles() (v []string) { + return p.Roles +} + +func (p *UserInfoResp) GetPermissions() (v []string) { + return p.Permissions +} + +var fieldIDToName_UserInfoResp = map[int16]string{ + 1: "loginId", + 2: "roles", + 3: "permissions", +} + +func (p *UserInfoResp) Read(iprot thrift.TProtocol) (err error) { + + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRING { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 2: + if fieldTypeId == thrift.LIST { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 3: + if fieldTypeId == thrift.LIST { + if err = p.ReadField3(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UserInfoResp[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *UserInfoResp) ReadField1(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.LoginId = _field + return nil +} +func (p *UserInfoResp) ReadField2(iprot thrift.TProtocol) error { + _, size, err := iprot.ReadListBegin() + if err != nil { + return err + } + _field := make([]string, 0, size) + for i := 0; i < size; i++ { + + var _elem string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _elem = v + } + + _field = append(_field, _elem) + } + if err := iprot.ReadListEnd(); err != nil { + return err + } + p.Roles = _field + return nil +} +func (p *UserInfoResp) ReadField3(iprot thrift.TProtocol) error { + _, size, err := iprot.ReadListBegin() + if err != nil { + return err + } + _field := make([]string, 0, size) + for i := 0; i < size; i++ { + + var _elem string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _elem = v + } + + _field = append(_field, _elem) + } + if err := iprot.ReadListEnd(); err != nil { + return err + } + p.Permissions = _field + return nil +} + +func (p *UserInfoResp) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("UserInfoResp"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } + if err = p.writeField3(oprot); err != nil { + fieldId = 3 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *UserInfoResp) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("loginId", thrift.STRING, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.LoginId); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} + +func (p *UserInfoResp) writeField2(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("roles", thrift.LIST, 2); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteListBegin(thrift.STRING, len(p.Roles)); err != nil { + return err + } + for _, v := range p.Roles { + if err := oprot.WriteString(v); err != nil { + return err + } + } + if err := oprot.WriteListEnd(); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} + +func (p *UserInfoResp) writeField3(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("permissions", thrift.LIST, 3); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteListBegin(thrift.STRING, len(p.Permissions)); err != nil { + return err + } + for _, v := range p.Permissions { + if err := oprot.WriteString(v); err != nil { + return err + } + } + if err := oprot.WriteListEnd(); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 end error: ", p), err) +} + +func (p *UserInfoResp) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("UserInfoResp(%+v)", *p) + +} + +type SensitiveResp struct { + Sensitive bool `thrift:"sensitive,1" form:"sensitive" json:"sensitive" query:"sensitive"` +} + +func NewSensitiveResp() *SensitiveResp { + return &SensitiveResp{} +} + +func (p *SensitiveResp) InitDefault() { +} + +func (p *SensitiveResp) GetSensitive() (v bool) { + return p.Sensitive +} + +var fieldIDToName_SensitiveResp = map[int16]string{ + 1: "sensitive", +} + +func (p *SensitiveResp) Read(iprot thrift.TProtocol) (err error) { + + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.BOOL { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_SensitiveResp[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *SensitiveResp) ReadField1(iprot thrift.TProtocol) error { + + var _field bool + if v, err := iprot.ReadBool(); err != nil { + return err + } else { + _field = v + } + p.Sensitive = _field + return nil +} + +func (p *SensitiveResp) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("SensitiveResp"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *SensitiveResp) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("sensitive", thrift.BOOL, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteBool(p.Sensitive); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} + +func (p *SensitiveResp) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("SensitiveResp(%+v)", *p) + +} + +type UserService interface { + Login(ctx context.Context, request *LoginReq) (r *LoginResp, err error) + + Public(ctx context.Context) (r *MessageResp, err error) + + UserInfo(ctx context.Context) (r *UserInfoResp, err error) + + Admin(ctx context.Context) (r *MessageResp, err error) + + Manager(ctx context.Context) (r *MessageResp, err error) + + Disable(ctx context.Context) (r *MessageResp, err error) + + Sensitive(ctx context.Context) (r *SensitiveResp, err error) +} + +type UserServiceClient struct { + c thrift.TClient +} + +func NewUserServiceClientFactory(t thrift.TTransport, f thrift.TProtocolFactory) *UserServiceClient { + return &UserServiceClient{ + c: thrift.NewTStandardClient(f.GetProtocol(t), f.GetProtocol(t)), + } +} + +func NewUserServiceClientProtocol(t thrift.TTransport, iprot thrift.TProtocol, oprot thrift.TProtocol) *UserServiceClient { + return &UserServiceClient{ + c: thrift.NewTStandardClient(iprot, oprot), + } +} + +func NewUserServiceClient(c thrift.TClient) *UserServiceClient { + return &UserServiceClient{ + c: c, + } +} + +func (p *UserServiceClient) Client_() thrift.TClient { + return p.c +} + +func (p *UserServiceClient) Login(ctx context.Context, request *LoginReq) (r *LoginResp, err error) { + var _args UserServiceLoginArgs + _args.Request = request + var _result UserServiceLoginResult + if err = p.Client_().Call(ctx, "Login", &_args, &_result); err != nil { + return + } + return _result.GetSuccess(), nil +} +func (p *UserServiceClient) Public(ctx context.Context) (r *MessageResp, err error) { + var _args UserServicePublicArgs + var _result UserServicePublicResult + if err = p.Client_().Call(ctx, "Public", &_args, &_result); err != nil { + return + } + return _result.GetSuccess(), nil +} +func (p *UserServiceClient) UserInfo(ctx context.Context) (r *UserInfoResp, err error) { + var _args UserServiceUserInfoArgs + var _result UserServiceUserInfoResult + if err = p.Client_().Call(ctx, "UserInfo", &_args, &_result); err != nil { + return + } + return _result.GetSuccess(), nil +} +func (p *UserServiceClient) Admin(ctx context.Context) (r *MessageResp, err error) { + var _args UserServiceAdminArgs + var _result UserServiceAdminResult + if err = p.Client_().Call(ctx, "Admin", &_args, &_result); err != nil { + return + } + return _result.GetSuccess(), nil +} +func (p *UserServiceClient) Manager(ctx context.Context) (r *MessageResp, err error) { + var _args UserServiceManagerArgs + var _result UserServiceManagerResult + if err = p.Client_().Call(ctx, "Manager", &_args, &_result); err != nil { + return + } + return _result.GetSuccess(), nil +} +func (p *UserServiceClient) Disable(ctx context.Context) (r *MessageResp, err error) { + var _args UserServiceDisableArgs + var _result UserServiceDisableResult + if err = p.Client_().Call(ctx, "Disable", &_args, &_result); err != nil { + return + } + return _result.GetSuccess(), nil +} +func (p *UserServiceClient) Sensitive(ctx context.Context) (r *SensitiveResp, err error) { + var _args UserServiceSensitiveArgs + var _result UserServiceSensitiveResult + if err = p.Client_().Call(ctx, "Sensitive", &_args, &_result); err != nil { + return + } + return _result.GetSuccess(), nil +} + +type UserServiceProcessor struct { + processorMap map[string]thrift.TProcessorFunction + handler UserService +} + +func (p *UserServiceProcessor) AddToProcessorMap(key string, processor thrift.TProcessorFunction) { + p.processorMap[key] = processor +} + +func (p *UserServiceProcessor) GetProcessorFunction(key string) (processor thrift.TProcessorFunction, ok bool) { + processor, ok = p.processorMap[key] + return processor, ok +} + +func (p *UserServiceProcessor) ProcessorMap() map[string]thrift.TProcessorFunction { + return p.processorMap +} + +func NewUserServiceProcessor(handler UserService) *UserServiceProcessor { + self := &UserServiceProcessor{handler: handler, processorMap: make(map[string]thrift.TProcessorFunction)} + self.AddToProcessorMap("Login", &userServiceProcessorLogin{handler: handler}) + self.AddToProcessorMap("Public", &userServiceProcessorPublic{handler: handler}) + self.AddToProcessorMap("UserInfo", &userServiceProcessorUserInfo{handler: handler}) + self.AddToProcessorMap("Admin", &userServiceProcessorAdmin{handler: handler}) + self.AddToProcessorMap("Manager", &userServiceProcessorManager{handler: handler}) + self.AddToProcessorMap("Disable", &userServiceProcessorDisable{handler: handler}) + self.AddToProcessorMap("Sensitive", &userServiceProcessorSensitive{handler: handler}) + return self +} +func (p *UserServiceProcessor) Process(ctx context.Context, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + name, _, seqId, err := iprot.ReadMessageBegin() + if err != nil { + return false, err + } + if processor, ok := p.GetProcessorFunction(name); ok { + return processor.Process(ctx, seqId, iprot, oprot) + } + iprot.Skip(thrift.STRUCT) + iprot.ReadMessageEnd() + x := thrift.NewTApplicationException(thrift.UNKNOWN_METHOD, "Unknown function "+name) + oprot.WriteMessageBegin(name, thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return false, x +} + +type userServiceProcessorLogin struct { + handler UserService +} + +func (p *userServiceProcessorLogin) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + args := UserServiceLoginArgs{} + if err = args.Read(iprot); err != nil { + iprot.ReadMessageEnd() + x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) + oprot.WriteMessageBegin("Login", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return false, err + } + + iprot.ReadMessageEnd() + var err2 error + result := UserServiceLoginResult{} + var retval *LoginResp + if retval, err2 = p.handler.Login(ctx, args.Request); err2 != nil { + x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing Login: "+err2.Error()) + oprot.WriteMessageBegin("Login", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return true, err2 + } else { + result.Success = retval + } + if err2 = oprot.WriteMessageBegin("Login", thrift.REPLY, seqId); err2 != nil { + err = err2 + } + if err2 = result.Write(oprot); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.Flush(ctx); err == nil && err2 != nil { + err = err2 + } + if err != nil { + return + } + return true, err +} + +type userServiceProcessorPublic struct { + handler UserService +} + +func (p *userServiceProcessorPublic) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + args := UserServicePublicArgs{} + if err = args.Read(iprot); err != nil { + iprot.ReadMessageEnd() + x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) + oprot.WriteMessageBegin("Public", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return false, err + } + + iprot.ReadMessageEnd() + var err2 error + result := UserServicePublicResult{} + var retval *MessageResp + if retval, err2 = p.handler.Public(ctx); err2 != nil { + x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing Public: "+err2.Error()) + oprot.WriteMessageBegin("Public", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return true, err2 + } else { + result.Success = retval + } + if err2 = oprot.WriteMessageBegin("Public", thrift.REPLY, seqId); err2 != nil { + err = err2 + } + if err2 = result.Write(oprot); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.Flush(ctx); err == nil && err2 != nil { + err = err2 + } + if err != nil { + return + } + return true, err +} + +type userServiceProcessorUserInfo struct { + handler UserService +} + +func (p *userServiceProcessorUserInfo) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + args := UserServiceUserInfoArgs{} + if err = args.Read(iprot); err != nil { + iprot.ReadMessageEnd() + x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) + oprot.WriteMessageBegin("UserInfo", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return false, err + } + + iprot.ReadMessageEnd() + var err2 error + result := UserServiceUserInfoResult{} + var retval *UserInfoResp + if retval, err2 = p.handler.UserInfo(ctx); err2 != nil { + x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing UserInfo: "+err2.Error()) + oprot.WriteMessageBegin("UserInfo", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return true, err2 + } else { + result.Success = retval + } + if err2 = oprot.WriteMessageBegin("UserInfo", thrift.REPLY, seqId); err2 != nil { + err = err2 + } + if err2 = result.Write(oprot); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.Flush(ctx); err == nil && err2 != nil { + err = err2 + } + if err != nil { + return + } + return true, err +} + +type userServiceProcessorAdmin struct { + handler UserService +} + +func (p *userServiceProcessorAdmin) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + args := UserServiceAdminArgs{} + if err = args.Read(iprot); err != nil { + iprot.ReadMessageEnd() + x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) + oprot.WriteMessageBegin("Admin", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return false, err + } + + iprot.ReadMessageEnd() + var err2 error + result := UserServiceAdminResult{} + var retval *MessageResp + if retval, err2 = p.handler.Admin(ctx); err2 != nil { + x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing Admin: "+err2.Error()) + oprot.WriteMessageBegin("Admin", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return true, err2 + } else { + result.Success = retval + } + if err2 = oprot.WriteMessageBegin("Admin", thrift.REPLY, seqId); err2 != nil { + err = err2 + } + if err2 = result.Write(oprot); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.Flush(ctx); err == nil && err2 != nil { + err = err2 + } + if err != nil { + return + } + return true, err +} + +type userServiceProcessorManager struct { + handler UserService +} + +func (p *userServiceProcessorManager) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + args := UserServiceManagerArgs{} + if err = args.Read(iprot); err != nil { + iprot.ReadMessageEnd() + x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) + oprot.WriteMessageBegin("Manager", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return false, err + } + + iprot.ReadMessageEnd() + var err2 error + result := UserServiceManagerResult{} + var retval *MessageResp + if retval, err2 = p.handler.Manager(ctx); err2 != nil { + x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing Manager: "+err2.Error()) + oprot.WriteMessageBegin("Manager", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return true, err2 + } else { + result.Success = retval + } + if err2 = oprot.WriteMessageBegin("Manager", thrift.REPLY, seqId); err2 != nil { + err = err2 + } + if err2 = result.Write(oprot); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.Flush(ctx); err == nil && err2 != nil { + err = err2 + } + if err != nil { + return + } + return true, err +} + +type userServiceProcessorDisable struct { + handler UserService +} + +func (p *userServiceProcessorDisable) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + args := UserServiceDisableArgs{} + if err = args.Read(iprot); err != nil { + iprot.ReadMessageEnd() + x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) + oprot.WriteMessageBegin("Disable", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return false, err + } + + iprot.ReadMessageEnd() + var err2 error + result := UserServiceDisableResult{} + var retval *MessageResp + if retval, err2 = p.handler.Disable(ctx); err2 != nil { + x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing Disable: "+err2.Error()) + oprot.WriteMessageBegin("Disable", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return true, err2 + } else { + result.Success = retval + } + if err2 = oprot.WriteMessageBegin("Disable", thrift.REPLY, seqId); err2 != nil { + err = err2 + } + if err2 = result.Write(oprot); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.Flush(ctx); err == nil && err2 != nil { + err = err2 + } + if err != nil { + return + } + return true, err +} + +type userServiceProcessorSensitive struct { + handler UserService +} + +func (p *userServiceProcessorSensitive) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + args := UserServiceSensitiveArgs{} + if err = args.Read(iprot); err != nil { + iprot.ReadMessageEnd() + x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) + oprot.WriteMessageBegin("Sensitive", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return false, err + } + + iprot.ReadMessageEnd() + var err2 error + result := UserServiceSensitiveResult{} + var retval *SensitiveResp + if retval, err2 = p.handler.Sensitive(ctx); err2 != nil { + x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing Sensitive: "+err2.Error()) + oprot.WriteMessageBegin("Sensitive", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return true, err2 + } else { + result.Success = retval + } + if err2 = oprot.WriteMessageBegin("Sensitive", thrift.REPLY, seqId); err2 != nil { + err = err2 + } + if err2 = result.Write(oprot); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.Flush(ctx); err == nil && err2 != nil { + err = err2 + } + if err != nil { + return + } + return true, err +} + +type UserServiceLoginArgs struct { + Request *LoginReq `thrift:"request,1"` +} + +func NewUserServiceLoginArgs() *UserServiceLoginArgs { + return &UserServiceLoginArgs{} +} + +func (p *UserServiceLoginArgs) InitDefault() { +} + +var UserServiceLoginArgs_Request_DEFAULT *LoginReq + +func (p *UserServiceLoginArgs) GetRequest() (v *LoginReq) { + if !p.IsSetRequest() { + return UserServiceLoginArgs_Request_DEFAULT + } + return p.Request +} + +var fieldIDToName_UserServiceLoginArgs = map[int16]string{ + 1: "request", +} + +func (p *UserServiceLoginArgs) IsSetRequest() bool { + return p.Request != nil +} + +func (p *UserServiceLoginArgs) Read(iprot thrift.TProtocol) (err error) { + + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UserServiceLoginArgs[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *UserServiceLoginArgs) ReadField1(iprot thrift.TProtocol) error { + _field := NewLoginReq() + if err := _field.Read(iprot); err != nil { + return err + } + p.Request = _field + return nil +} + +func (p *UserServiceLoginArgs) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("Login_args"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *UserServiceLoginArgs) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("request", thrift.STRUCT, 1); err != nil { + goto WriteFieldBeginError + } + if err := p.Request.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} + +func (p *UserServiceLoginArgs) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("UserServiceLoginArgs(%+v)", *p) + +} + +type UserServiceLoginResult struct { + Success *LoginResp `thrift:"success,0,optional"` +} + +func NewUserServiceLoginResult() *UserServiceLoginResult { + return &UserServiceLoginResult{} +} + +func (p *UserServiceLoginResult) InitDefault() { +} + +var UserServiceLoginResult_Success_DEFAULT *LoginResp + +func (p *UserServiceLoginResult) GetSuccess() (v *LoginResp) { + if !p.IsSetSuccess() { + return UserServiceLoginResult_Success_DEFAULT + } + return p.Success +} + +var fieldIDToName_UserServiceLoginResult = map[int16]string{ + 0: "success", +} + +func (p *UserServiceLoginResult) IsSetSuccess() bool { + return p.Success != nil +} + +func (p *UserServiceLoginResult) Read(iprot thrift.TProtocol) (err error) { + + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 0: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField0(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UserServiceLoginResult[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *UserServiceLoginResult) ReadField0(iprot thrift.TProtocol) error { + _field := NewLoginResp() + if err := _field.Read(iprot); err != nil { + return err + } + p.Success = _field + return nil +} + +func (p *UserServiceLoginResult) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("Login_result"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField0(oprot); err != nil { + fieldId = 0 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *UserServiceLoginResult) writeField0(oprot thrift.TProtocol) (err error) { + if p.IsSetSuccess() { + if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil { + goto WriteFieldBeginError + } + if err := p.Success.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err) +} + +func (p *UserServiceLoginResult) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("UserServiceLoginResult(%+v)", *p) + +} + +type UserServicePublicArgs struct { +} + +func NewUserServicePublicArgs() *UserServicePublicArgs { + return &UserServicePublicArgs{} +} + +func (p *UserServicePublicArgs) InitDefault() { +} + +var fieldIDToName_UserServicePublicArgs = map[int16]string{} + +func (p *UserServicePublicArgs) Read(iprot thrift.TProtocol) (err error) { + + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldTypeError + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +SkipFieldTypeError: + return thrift.PrependError(fmt.Sprintf("%T skip field type %d error", p, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *UserServicePublicArgs) Write(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteStructBegin("Public_args"); err != nil { + goto WriteStructBeginError + } + if p != nil { + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *UserServicePublicArgs) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("UserServicePublicArgs(%+v)", *p) + +} + +type UserServicePublicResult struct { + Success *MessageResp `thrift:"success,0,optional"` +} + +func NewUserServicePublicResult() *UserServicePublicResult { + return &UserServicePublicResult{} +} + +func (p *UserServicePublicResult) InitDefault() { +} + +var UserServicePublicResult_Success_DEFAULT *MessageResp + +func (p *UserServicePublicResult) GetSuccess() (v *MessageResp) { + if !p.IsSetSuccess() { + return UserServicePublicResult_Success_DEFAULT + } + return p.Success +} + +var fieldIDToName_UserServicePublicResult = map[int16]string{ + 0: "success", +} + +func (p *UserServicePublicResult) IsSetSuccess() bool { + return p.Success != nil +} + +func (p *UserServicePublicResult) Read(iprot thrift.TProtocol) (err error) { + + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 0: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField0(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UserServicePublicResult[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *UserServicePublicResult) ReadField0(iprot thrift.TProtocol) error { + _field := NewMessageResp() + if err := _field.Read(iprot); err != nil { + return err + } + p.Success = _field + return nil +} + +func (p *UserServicePublicResult) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("Public_result"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField0(oprot); err != nil { + fieldId = 0 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *UserServicePublicResult) writeField0(oprot thrift.TProtocol) (err error) { + if p.IsSetSuccess() { + if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil { + goto WriteFieldBeginError + } + if err := p.Success.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err) +} + +func (p *UserServicePublicResult) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("UserServicePublicResult(%+v)", *p) + +} + +type UserServiceUserInfoArgs struct { +} + +func NewUserServiceUserInfoArgs() *UserServiceUserInfoArgs { + return &UserServiceUserInfoArgs{} +} + +func (p *UserServiceUserInfoArgs) InitDefault() { +} + +var fieldIDToName_UserServiceUserInfoArgs = map[int16]string{} + +func (p *UserServiceUserInfoArgs) Read(iprot thrift.TProtocol) (err error) { + + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldTypeError + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +SkipFieldTypeError: + return thrift.PrependError(fmt.Sprintf("%T skip field type %d error", p, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *UserServiceUserInfoArgs) Write(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteStructBegin("UserInfo_args"); err != nil { + goto WriteStructBeginError + } + if p != nil { + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *UserServiceUserInfoArgs) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("UserServiceUserInfoArgs(%+v)", *p) + +} + +type UserServiceUserInfoResult struct { + Success *UserInfoResp `thrift:"success,0,optional"` +} + +func NewUserServiceUserInfoResult() *UserServiceUserInfoResult { + return &UserServiceUserInfoResult{} +} + +func (p *UserServiceUserInfoResult) InitDefault() { +} + +var UserServiceUserInfoResult_Success_DEFAULT *UserInfoResp + +func (p *UserServiceUserInfoResult) GetSuccess() (v *UserInfoResp) { + if !p.IsSetSuccess() { + return UserServiceUserInfoResult_Success_DEFAULT + } + return p.Success +} + +var fieldIDToName_UserServiceUserInfoResult = map[int16]string{ + 0: "success", +} + +func (p *UserServiceUserInfoResult) IsSetSuccess() bool { + return p.Success != nil +} + +func (p *UserServiceUserInfoResult) Read(iprot thrift.TProtocol) (err error) { + + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 0: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField0(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UserServiceUserInfoResult[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *UserServiceUserInfoResult) ReadField0(iprot thrift.TProtocol) error { + _field := NewUserInfoResp() + if err := _field.Read(iprot); err != nil { + return err + } + p.Success = _field + return nil +} + +func (p *UserServiceUserInfoResult) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("UserInfo_result"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField0(oprot); err != nil { + fieldId = 0 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *UserServiceUserInfoResult) writeField0(oprot thrift.TProtocol) (err error) { + if p.IsSetSuccess() { + if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil { + goto WriteFieldBeginError + } + if err := p.Success.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err) +} + +func (p *UserServiceUserInfoResult) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("UserServiceUserInfoResult(%+v)", *p) + +} + +type UserServiceAdminArgs struct { +} + +func NewUserServiceAdminArgs() *UserServiceAdminArgs { + return &UserServiceAdminArgs{} +} + +func (p *UserServiceAdminArgs) InitDefault() { +} + +var fieldIDToName_UserServiceAdminArgs = map[int16]string{} + +func (p *UserServiceAdminArgs) Read(iprot thrift.TProtocol) (err error) { + + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldTypeError + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +SkipFieldTypeError: + return thrift.PrependError(fmt.Sprintf("%T skip field type %d error", p, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *UserServiceAdminArgs) Write(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteStructBegin("Admin_args"); err != nil { + goto WriteStructBeginError + } + if p != nil { + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *UserServiceAdminArgs) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("UserServiceAdminArgs(%+v)", *p) + +} + +type UserServiceAdminResult struct { + Success *MessageResp `thrift:"success,0,optional"` +} + +func NewUserServiceAdminResult() *UserServiceAdminResult { + return &UserServiceAdminResult{} +} + +func (p *UserServiceAdminResult) InitDefault() { +} + +var UserServiceAdminResult_Success_DEFAULT *MessageResp + +func (p *UserServiceAdminResult) GetSuccess() (v *MessageResp) { + if !p.IsSetSuccess() { + return UserServiceAdminResult_Success_DEFAULT + } + return p.Success +} + +var fieldIDToName_UserServiceAdminResult = map[int16]string{ + 0: "success", +} + +func (p *UserServiceAdminResult) IsSetSuccess() bool { + return p.Success != nil +} + +func (p *UserServiceAdminResult) Read(iprot thrift.TProtocol) (err error) { + + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 0: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField0(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UserServiceAdminResult[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *UserServiceAdminResult) ReadField0(iprot thrift.TProtocol) error { + _field := NewMessageResp() + if err := _field.Read(iprot); err != nil { + return err + } + p.Success = _field + return nil +} + +func (p *UserServiceAdminResult) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("Admin_result"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField0(oprot); err != nil { + fieldId = 0 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *UserServiceAdminResult) writeField0(oprot thrift.TProtocol) (err error) { + if p.IsSetSuccess() { + if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil { + goto WriteFieldBeginError + } + if err := p.Success.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err) +} + +func (p *UserServiceAdminResult) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("UserServiceAdminResult(%+v)", *p) + +} + +type UserServiceManagerArgs struct { +} + +func NewUserServiceManagerArgs() *UserServiceManagerArgs { + return &UserServiceManagerArgs{} +} + +func (p *UserServiceManagerArgs) InitDefault() { +} + +var fieldIDToName_UserServiceManagerArgs = map[int16]string{} + +func (p *UserServiceManagerArgs) Read(iprot thrift.TProtocol) (err error) { + + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldTypeError + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +SkipFieldTypeError: + return thrift.PrependError(fmt.Sprintf("%T skip field type %d error", p, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *UserServiceManagerArgs) Write(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteStructBegin("Manager_args"); err != nil { + goto WriteStructBeginError + } + if p != nil { + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *UserServiceManagerArgs) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("UserServiceManagerArgs(%+v)", *p) + +} + +type UserServiceManagerResult struct { + Success *MessageResp `thrift:"success,0,optional"` +} + +func NewUserServiceManagerResult() *UserServiceManagerResult { + return &UserServiceManagerResult{} +} + +func (p *UserServiceManagerResult) InitDefault() { +} + +var UserServiceManagerResult_Success_DEFAULT *MessageResp + +func (p *UserServiceManagerResult) GetSuccess() (v *MessageResp) { + if !p.IsSetSuccess() { + return UserServiceManagerResult_Success_DEFAULT + } + return p.Success +} + +var fieldIDToName_UserServiceManagerResult = map[int16]string{ + 0: "success", +} + +func (p *UserServiceManagerResult) IsSetSuccess() bool { + return p.Success != nil +} + +func (p *UserServiceManagerResult) Read(iprot thrift.TProtocol) (err error) { + + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 0: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField0(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UserServiceManagerResult[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *UserServiceManagerResult) ReadField0(iprot thrift.TProtocol) error { + _field := NewMessageResp() + if err := _field.Read(iprot); err != nil { + return err + } + p.Success = _field + return nil +} + +func (p *UserServiceManagerResult) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("Manager_result"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField0(oprot); err != nil { + fieldId = 0 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *UserServiceManagerResult) writeField0(oprot thrift.TProtocol) (err error) { + if p.IsSetSuccess() { + if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil { + goto WriteFieldBeginError + } + if err := p.Success.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err) +} + +func (p *UserServiceManagerResult) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("UserServiceManagerResult(%+v)", *p) + +} + +type UserServiceDisableArgs struct { +} + +func NewUserServiceDisableArgs() *UserServiceDisableArgs { + return &UserServiceDisableArgs{} +} + +func (p *UserServiceDisableArgs) InitDefault() { +} + +var fieldIDToName_UserServiceDisableArgs = map[int16]string{} + +func (p *UserServiceDisableArgs) Read(iprot thrift.TProtocol) (err error) { + + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldTypeError + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +SkipFieldTypeError: + return thrift.PrependError(fmt.Sprintf("%T skip field type %d error", p, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *UserServiceDisableArgs) Write(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteStructBegin("Disable_args"); err != nil { + goto WriteStructBeginError + } + if p != nil { + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *UserServiceDisableArgs) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("UserServiceDisableArgs(%+v)", *p) + +} + +type UserServiceDisableResult struct { + Success *MessageResp `thrift:"success,0,optional"` +} + +func NewUserServiceDisableResult() *UserServiceDisableResult { + return &UserServiceDisableResult{} +} + +func (p *UserServiceDisableResult) InitDefault() { +} + +var UserServiceDisableResult_Success_DEFAULT *MessageResp + +func (p *UserServiceDisableResult) GetSuccess() (v *MessageResp) { + if !p.IsSetSuccess() { + return UserServiceDisableResult_Success_DEFAULT + } + return p.Success +} + +var fieldIDToName_UserServiceDisableResult = map[int16]string{ + 0: "success", +} + +func (p *UserServiceDisableResult) IsSetSuccess() bool { + return p.Success != nil +} + +func (p *UserServiceDisableResult) Read(iprot thrift.TProtocol) (err error) { + + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 0: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField0(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UserServiceDisableResult[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *UserServiceDisableResult) ReadField0(iprot thrift.TProtocol) error { + _field := NewMessageResp() + if err := _field.Read(iprot); err != nil { + return err + } + p.Success = _field + return nil +} + +func (p *UserServiceDisableResult) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("Disable_result"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField0(oprot); err != nil { + fieldId = 0 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *UserServiceDisableResult) writeField0(oprot thrift.TProtocol) (err error) { + if p.IsSetSuccess() { + if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil { + goto WriteFieldBeginError + } + if err := p.Success.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err) +} + +func (p *UserServiceDisableResult) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("UserServiceDisableResult(%+v)", *p) + +} + +type UserServiceSensitiveArgs struct { +} + +func NewUserServiceSensitiveArgs() *UserServiceSensitiveArgs { + return &UserServiceSensitiveArgs{} +} + +func (p *UserServiceSensitiveArgs) InitDefault() { +} + +var fieldIDToName_UserServiceSensitiveArgs = map[int16]string{} + +func (p *UserServiceSensitiveArgs) Read(iprot thrift.TProtocol) (err error) { + + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldTypeError + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +SkipFieldTypeError: + return thrift.PrependError(fmt.Sprintf("%T skip field type %d error", p, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *UserServiceSensitiveArgs) Write(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteStructBegin("Sensitive_args"); err != nil { + goto WriteStructBeginError + } + if p != nil { + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *UserServiceSensitiveArgs) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("UserServiceSensitiveArgs(%+v)", *p) + +} + +type UserServiceSensitiveResult struct { + Success *SensitiveResp `thrift:"success,0,optional"` +} + +func NewUserServiceSensitiveResult() *UserServiceSensitiveResult { + return &UserServiceSensitiveResult{} +} + +func (p *UserServiceSensitiveResult) InitDefault() { +} + +var UserServiceSensitiveResult_Success_DEFAULT *SensitiveResp + +func (p *UserServiceSensitiveResult) GetSuccess() (v *SensitiveResp) { + if !p.IsSetSuccess() { + return UserServiceSensitiveResult_Success_DEFAULT + } + return p.Success +} + +var fieldIDToName_UserServiceSensitiveResult = map[int16]string{ + 0: "success", +} + +func (p *UserServiceSensitiveResult) IsSetSuccess() bool { + return p.Success != nil +} + +func (p *UserServiceSensitiveResult) Read(iprot thrift.TProtocol) (err error) { + + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 0: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField0(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UserServiceSensitiveResult[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *UserServiceSensitiveResult) ReadField0(iprot thrift.TProtocol) error { + _field := NewSensitiveResp() + if err := _field.Read(iprot); err != nil { + return err + } + p.Success = _field + return nil +} + +func (p *UserServiceSensitiveResult) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("Sensitive_result"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField0(oprot); err != nil { + fieldId = 0 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *UserServiceSensitiveResult) writeField0(oprot thrift.TProtocol) (err error) { + if p.IsSetSuccess() { + if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil { + goto WriteFieldBeginError + } + if err := p.Success.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err) +} + +func (p *UserServiceSensitiveResult) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("UserServiceSensitiveResult(%+v)", *p) + +} diff --git a/examples/hertz/herz-example/biz/router/register.go b/examples/hertz/herz-example/biz/router/register.go new file mode 100644 index 0000000000000000000000000000000000000000..242cfd9ce1c9d70a4e0f9a17fa6975fe1b2e1870 --- /dev/null +++ b/examples/hertz/herz-example/biz/router/register.go @@ -0,0 +1,15 @@ +// Code generated by hertz generator. DO NOT EDIT. + +package router + +import ( + user "github.com/click33/sa-token-go/examples/hertz/herz-example/biz/router/user" + "github.com/cloudwego/hertz/pkg/app/server" +) + +// GeneratedRegister registers routers generated by IDL. +func GeneratedRegister(r *server.Hertz) { + //INSERT_POINT: DO NOT DELETE THIS LINE! + user.Register(r) + +} diff --git a/examples/hertz/herz-example/biz/router/user/middleware.go b/examples/hertz/herz-example/biz/router/user/middleware.go new file mode 100644 index 0000000000000000000000000000000000000000..da94324d79a27ac310f82c774a508ad3f1575c27 --- /dev/null +++ b/examples/hertz/herz-example/biz/router/user/middleware.go @@ -0,0 +1,47 @@ +// Code generated by hertz generator. + +package user + +import ( + sahertz "github.com/click33/sa-token-go/integrations/hertz" + "github.com/cloudwego/hertz/pkg/app" +) + +func rootMw() []app.HandlerFunc { + // your code... + return nil +} + +func _adminMw() []app.HandlerFunc { + // your code... + return []app.HandlerFunc{sahertz.CheckPermission("admin:*")} +} + +func _loginMw() []app.HandlerFunc { + // your code... + return nil +} + +func _managerMw() []app.HandlerFunc { + // your code... + return []app.HandlerFunc{sahertz.CheckRole("manager")} +} + +func _publicMw() []app.HandlerFunc { + return []app.HandlerFunc{sahertz.Ignore()} +} + +func _sensitiveMw() []app.HandlerFunc { + // your code... + return []app.HandlerFunc{sahertz.CheckDisable()} +} + +func _userinfoMw() []app.HandlerFunc { + // your code... + return []app.HandlerFunc{sahertz.CheckLogin()} +} + +func _disableMw() []app.HandlerFunc { + // your code... + return nil +} diff --git a/examples/hertz/herz-example/biz/router/user/user.go b/examples/hertz/herz-example/biz/router/user/user.go new file mode 100644 index 0000000000000000000000000000000000000000..04ddee4a7f534cfd5d3f2e05473d33d08baabda0 --- /dev/null +++ b/examples/hertz/herz-example/biz/router/user/user.go @@ -0,0 +1,27 @@ +// Code generated by hertz generator. DO NOT EDIT. + +package user + +import ( + user "github.com/click33/sa-token-go/examples/hertz/herz-example/biz/handler/user" + "github.com/cloudwego/hertz/pkg/app/server" +) + +/* + This file will register all the routes of the services in the master idl. + And it will update automatically when you use the "update" command for the idl. + So don't modify the contents of the file, or your code will be deleted when it is updated. +*/ + +// Register register routes based on the IDL 'api.${HTTP Method}' annotation. +func Register(r *server.Hertz) { + + root := r.Group("/", rootMw()...) + root.GET("/admin", append(_adminMw(), user.Admin)...) + root.GET("/disable", append(_disableMw(), user.Disable)...) + root.GET("/login", append(_loginMw(), user.Login)...) + root.GET("/manager", append(_managerMw(), user.Manager)...) + root.GET("/public", append(_publicMw(), user.Public)...) + root.GET("/sensitive", append(_sensitiveMw(), user.Sensitive)...) + root.GET("/user", append(_userinfoMw(), user.UserInfo)...) +} diff --git a/examples/hertz/herz-example/build.sh b/examples/hertz/herz-example/build.sh new file mode 100755 index 0000000000000000000000000000000000000000..f1ba589caace36141efadaf899c63353d6e5f7ea --- /dev/null +++ b/examples/hertz/herz-example/build.sh @@ -0,0 +1,6 @@ +#!/bin/bash +RUN_NAME=hertz_service +mkdir -p output/bin +cp script/* output 2>/dev/null +chmod +x output/bootstrap.sh +go build -o output/bin/${RUN_NAME} \ No newline at end of file diff --git a/examples/hertz/herz-example/go.mod b/examples/hertz/herz-example/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..809fbba379f3966c0b6f1e01317f4f2e38b41ad8 --- /dev/null +++ b/examples/hertz/herz-example/go.mod @@ -0,0 +1,28 @@ +module github.com/click33/sa-token-go/examples/hertz/herz-example + +go 1.25.4 + +require github.com/cloudwego/hertz v0.10.3 + +require ( + github.com/apache/thrift v0.22.0 // indirect + github.com/bytedance/gopkg v0.1.1 // indirect + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect + github.com/cloudwego/gopkg v0.1.4 // indirect + github.com/cloudwego/netpoll v0.7.0 // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/golang/protobuf v1.5.0 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/nyaruka/phonenumbers v1.0.55 // indirect + github.com/tidwall/gjson v1.14.4 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect + golang.org/x/sys v0.24.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect +) + +replace github.com/apache/thrift => github.com/apache/thrift v0.13.0 diff --git a/examples/hertz/herz-example/go.sum b/examples/hertz/herz-example/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..3921167ec6d24efff6fb2821251dd88d5e77c608 --- /dev/null +++ b/examples/hertz/herz-example/go.sum @@ -0,0 +1,119 @@ +github.com/apache/thrift v0.22.0 h1:r7mTJdj51TMDe6RtcmNdQxgn9XcyfGDOzegMDRg47uc= +github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJeynQ+/g= +github.com/bytedance/gopkg v0.1.1 h1:3azzgSkiaw79u24a+w9arfH8OfnQQ4MHUt9lJFREEaE= +github.com/bytedance/gopkg v0.1.1/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/gopkg v0.1.4 h1:EoQiCG4sTonTPHxOGE0VlQs+sQR+Hsi2uN0qqwu8O50= +github.com/cloudwego/gopkg v0.1.4/go.mod h1:FQuXsRWRsSqJLsMVd5SYzp8/Z1y5gXKnVvRrWUOsCMI= +github.com/cloudwego/hertz v0.10.3 h1:NFcQAjouVJsod79XPLC/PaFfHgjMTYbiErmW+vGBi8A= +github.com/cloudwego/hertz v0.10.3/go.mod h1:W5dUFXZPZkyfjMMo3EQrMQbofuvTsctM9IxmhbkuT18= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cloudwego/netpoll v0.7.0 h1:bDrxQaNfijRI1zyGgXHQoE/nYegL0nr+ijO1Norelc4= +github.com/cloudwego/netpoll v0.7.0/go.mod h1:PI+YrmyS7cIr0+SD4seJz3Eo3ckkXdu2ZVKBLhURLNU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/nyaruka/phonenumbers v1.0.55 h1:bj0nTO88Y68KeUQ/n3Lo2KgK7lM1hF7L9NFuwcCl3yg= +github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/examples/hertz/herz-example/idl/user.thrift b/examples/hertz/herz-example/idl/user.thrift new file mode 100644 index 0000000000000000000000000000000000000000..9559c235b27138d07063770bfcd19bb817250614 --- /dev/null +++ b/examples/hertz/herz-example/idl/user.thrift @@ -0,0 +1,33 @@ +namespace go user + +struct LoginReq { + 1: string userId (api.query="userId"); +} + +struct LoginResp { + 1: string token; +} + +struct MessageResp { + 1: string message; +} + +struct UserInfoResp { + 1: string loginId; + 2: list roles; + 3: list permissions; +} + +struct SensitiveResp { + 1: bool sensitive; +} + +service UserService { + LoginResp Login(1: LoginReq request) (api.get="/login"); + MessageResp Public() (api.get="/public"); + UserInfoResp UserInfo() (api.get="/user"); + MessageResp Admin() (api.get="/admin"); + MessageResp Manager() (api.get="/manager"); + MessageResp Disable() (api.get="/disable"); + SensitiveResp Sensitive() (api.get="/sensitive"); +} \ No newline at end of file diff --git a/examples/hertz/herz-example/main.go b/examples/hertz/herz-example/main.go new file mode 100644 index 0000000000000000000000000000000000000000..8eeb809560c14cf43ca4319f232bd9496c75d02d --- /dev/null +++ b/examples/hertz/herz-example/main.go @@ -0,0 +1,22 @@ +// Code generated by hertz generator. + +package main + +import ( + sahertz "github.com/click33/sa-token-go/integrations/hertz" // 只需这一个导入! + "github.com/click33/sa-token-go/storage/memory" + "github.com/cloudwego/hertz/pkg/app/server" +) + +func main() { + // 初始化(所有功能都在 sahertz 包中) + storage := memory.NewStorage() + config := sahertz.DefaultConfig() // 使用 sahertz.DefaultConfig + manager := sahertz.NewManager(storage, config) // 使用 sahertz.NewManager + sahertz.SetManager(manager) // 使用 sahertz.SetManager + + h := server.Default() + + register(h) + h.Spin() +} diff --git a/examples/hertz/herz-example/router.go b/examples/hertz/herz-example/router.go new file mode 100644 index 0000000000000000000000000000000000000000..3b89cc7669150e5258d1e9fd393df41b1114f928 --- /dev/null +++ b/examples/hertz/herz-example/router.go @@ -0,0 +1,14 @@ +// Code generated by hertz generator. + +package main + +import ( + "github.com/cloudwego/hertz/pkg/app/server" +) + +// customizeRegister registers customize routers. +func customizedRegister(r *server.Hertz) { + //r.GET("/ping", handler.Ping) + + // your code ... +} diff --git a/examples/hertz/herz-example/router_gen.go b/examples/hertz/herz-example/router_gen.go new file mode 100644 index 0000000000000000000000000000000000000000..83a8d0cd6dbae50199d0bfd7abc998cbdf641d03 --- /dev/null +++ b/examples/hertz/herz-example/router_gen.go @@ -0,0 +1,16 @@ +// Code generated by hertz generator. DO NOT EDIT. + +package main + +import ( + router "github.com/click33/sa-token-go/examples/hertz/herz-example/biz/router" + "github.com/cloudwego/hertz/pkg/app/server" +) + +// register registers all routers. +func register(r *server.Hertz) { + + router.GeneratedRegister(r) + + customizedRegister(r) +} diff --git a/examples/hertz/herz-example/script/bootstrap.sh b/examples/hertz/herz-example/script/bootstrap.sh new file mode 100755 index 0000000000000000000000000000000000000000..3f3fc1afaad9be27f5da4298d1d2c3d2a749f1f5 --- /dev/null +++ b/examples/hertz/herz-example/script/bootstrap.sh @@ -0,0 +1,5 @@ +#!/bin/bash +CURDIR=$(cd $(dirname $0); pwd) +BinaryName=hertz_service +echo "$CURDIR/bin/${BinaryName}" +exec $CURDIR/bin/${BinaryName} \ No newline at end of file diff --git a/examples/jwt-example/go.mod b/examples/jwt-example/go.mod index 1daf0cfc076da081747e0d23ef4a23c49e8b74fc..ff34abb20c2ab79cecef6533ae10cdc78fd89125 100644 --- a/examples/jwt-example/go.mod +++ b/examples/jwt-example/go.mod @@ -3,9 +3,9 @@ module github.com/click33/sa-token-go/examples/jwt-example go 1.21 require ( - github.com/click33/sa-token-go/core v0.1.3 - github.com/click33/sa-token-go/storage/memory v0.1.3 - github.com/click33/sa-token-go/stputil v0.1.3 + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/storage/memory v0.1.8 + github.com/click33/sa-token-go/stputil v0.1.8 ) require ( diff --git a/examples/listener-example/go.mod b/examples/listener-example/go.mod index 728dd7ce2c715735dfba69ac8425fec8a65b6ed9..9a6de24871e95d8e621c05bec6e4d28c606a4664 100644 --- a/examples/listener-example/go.mod +++ b/examples/listener-example/go.mod @@ -3,9 +3,9 @@ module github.com/click33/sa-token-go/examples/listener-example go 1.21 require ( - github.com/click33/sa-token-go/core v0.1.3 - github.com/click33/sa-token-go/storage/memory v0.1.3 - github.com/click33/sa-token-go/stputil v0.1.3 + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/storage/memory v0.1.8 + github.com/click33/sa-token-go/stputil v0.1.8 ) require ( diff --git a/examples/multi-certification/authkit/stpkit.go b/examples/multi-certification/authkit/stpkit.go new file mode 100644 index 0000000000000000000000000000000000000000..4cad6bedb6641eb7af2a12b14e4a5b84417f01a1 --- /dev/null +++ b/examples/multi-certification/authkit/stpkit.go @@ -0,0 +1,9 @@ +package authkit + +import "github.com/click33/sa-token-go/stputil" + +var ( + ADMIN *stputil.StpLogic + USER *stputil.StpLogic + OTHER *stputil.StpLogic +) diff --git a/examples/multi-certification/go.mod b/examples/multi-certification/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..c0b305ff8f7678b54678e377cf7ad7ec66060b94 --- /dev/null +++ b/examples/multi-certification/go.mod @@ -0,0 +1,22 @@ +module github.com/click33/sa-token-go/examples/multi-certification + +go 1.25.3 + +require ( + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/storage/memory v0.1.8 + github.com/click33/sa-token-go/stputil v0.1.8 +) + +require ( + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/panjf2000/ants/v2 v2.11.3 // indirect + golang.org/x/sync v0.19.0 // indirect +) + +replace ( + github.com/click33/sa-token-go/core => ../../core + github.com/click33/sa-token-go/storage/memory => ../../storage/memory + github.com/click33/sa-token-go/stputil => ../../stputil +) diff --git a/examples/multi-certification/go.sum b/examples/multi-certification/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..99a4055fbd7a701caa44d5459c093677e46239d9 --- /dev/null +++ b/examples/multi-certification/go.sum @@ -0,0 +1,16 @@ +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg= +github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/multi-certification/main.go b/examples/multi-certification/main.go new file mode 100644 index 0000000000000000000000000000000000000000..a959ade641422fcd31372c73fdee5a56b87eef08 --- /dev/null +++ b/examples/multi-certification/main.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/examples/multi-certification/authkit" + "github.com/click33/sa-token-go/storage/memory" + "github.com/click33/sa-token-go/stputil" +) + +func main() { + // 注意 多认证体现需要将不同的manager的KeyPrefix设置为不同的值 + storage := memory.NewStorage() + userManager := core.NewBuilder(). + Storage(storage). + Timeout(6600). + IsPrintBanner(false). + KeyPrefix("user"). // 要唯一 + Build() + + adminManager := core.NewBuilder(). + Storage(storage). + Timeout(3600). + IsPrintBanner(false). + KeyPrefix("admin"). // 要唯一 + TokenStyle(core.TokenStyleTik). + Build() + + authkit.ADMIN = stputil.NewStpLogic(adminManager) + authkit.USER = stputil.NewStpLogic(userManager) + + Run() +} + +func Run() { + userTokenValue, _ := authkit.USER.Login("ID1") + adminTokenValue, _ := authkit.ADMIN.Login("ID1") + fmt.Println("userTokenValue:", userTokenValue) + fmt.Println("adminTokenValue:", adminTokenValue) + + _ = authkit.ADMIN.SetPermissions("ID1", []string{"admin1", "admin2"}) + _ = authkit.USER.SetPermissions("ID1", []string{"user1", "user2"}) + adminPermissions, _ := authkit.ADMIN.GetPermissions("ID1") + userPermissions, _ := authkit.USER.GetPermissions("ID1") + fmt.Println("admin permissions:", adminPermissions) + fmt.Println("user permissions:", userPermissions) + + fmt.Println("admin has user1 permission:", authkit.ADMIN.HasPermission("ID1", "user1")) + fmt.Println("admin has admin1 permission:", authkit.ADMIN.HasPermission("ID1", "admin1")) + fmt.Println("user has admin1 permission:", authkit.USER.HasPermission("ID1", "admin1")) + fmt.Println("user has user1 permission:", authkit.USER.HasPermission("ID1", "user1")) +} diff --git a/examples/oauth2-example/go.mod b/examples/oauth2-example/go.mod index 905f73e0f9d951e52881a2e939ca6789750bc4af..a2bd77c61afe8ce657c6872fb503836a5d488356 100644 --- a/examples/oauth2-example/go.mod +++ b/examples/oauth2-example/go.mod @@ -3,8 +3,8 @@ module github.com/click33/sa-token-go/examples/oauth2-example go 1.21 require ( - github.com/click33/sa-token-go/core v0.1.3 - github.com/click33/sa-token-go/storage/memory v0.1.3 + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/storage/memory v0.1.8 github.com/gin-gonic/gin v1.10.0 ) diff --git a/examples/quick-start/simple-example/go.mod b/examples/quick-start/simple-example/go.mod index e7532cbb8f306a51ee311ec18c31bec841ea829d..a0fb35e2d3641e6ca5d135863f2a73a7d65eb285 100644 --- a/examples/quick-start/simple-example/go.mod +++ b/examples/quick-start/simple-example/go.mod @@ -3,9 +3,9 @@ module github.com/click33/sa-token-go/examples/simple-example go 1.21 require ( - github.com/click33/sa-token-go/core v0.1.3 - github.com/click33/sa-token-go/storage/memory v0.1.3 - github.com/click33/sa-token-go/stputil v0.1.3 + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/storage/memory v0.1.8 + github.com/click33/sa-token-go/stputil v0.1.8 ) require ( diff --git a/examples/redis-example/go.mod b/examples/redis-example/go.mod index 4020f16b05991b2944ca045a922d58a4b1d7ee10..a2dfc0109f22912054b8c6df94bde33f3e05f5a4 100644 --- a/examples/redis-example/go.mod +++ b/examples/redis-example/go.mod @@ -3,9 +3,9 @@ module github.com/click33/sa-token-go/examples/redis-example go 1.21 require ( - github.com/click33/sa-token-go/core v0.1.3 - github.com/click33/sa-token-go/storage/redis v0.1.3 - github.com/click33/sa-token-go/stputil v0.1.3 + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/storage/redis v0.1.8 + github.com/click33/sa-token-go/stputil v0.1.8 github.com/redis/go-redis/v9 v9.5.1 ) diff --git a/examples/security-features/go.mod b/examples/security-features/go.mod index 387f4e015cb9cbd31d88113b94168f9cf5f14083..edf37392b91797469103d7e947892523aaaac6b5 100644 --- a/examples/security-features/go.mod +++ b/examples/security-features/go.mod @@ -3,9 +3,9 @@ module github.com/click33/sa-token-go/examples/security-features go 1.21 require ( - github.com/click33/sa-token-go/core v0.1.3 - github.com/click33/sa-token-go/storage/memory v0.1.3 - github.com/click33/sa-token-go/stputil v0.1.3 + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/storage/memory v0.1.8 + github.com/click33/sa-token-go/stputil v0.1.8 ) require ( diff --git a/examples/session-demo/go.mod b/examples/session-demo/go.mod index 481560ea54ec29cbd8564d879ecce98e0cecd086..da1fa9d173ea85da6245184cd0b9f6f39da6ff02 100644 --- a/examples/session-demo/go.mod +++ b/examples/session-demo/go.mod @@ -3,9 +3,9 @@ module github.com/click33/sa-token-go/examples/session-demo go 1.21 require ( - github.com/click33/sa-token-go/core v0.1.3 - github.com/click33/sa-token-go/storage/memory v0.1.3 - github.com/click33/sa-token-go/stputil v0.1.3 + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/storage/memory v0.1.8 + github.com/click33/sa-token-go/stputil v0.1.8 ) replace ( diff --git a/examples/token-styles/go.mod b/examples/token-styles/go.mod index 2f16adedbbec1c3bd15f6a226b86398ec213b196..916d663279acd4d6f6ab0cec98d7f66034d88c3c 100644 --- a/examples/token-styles/go.mod +++ b/examples/token-styles/go.mod @@ -3,9 +3,9 @@ module github.com/click33/sa-token-go/examples/token-styles go 1.21 require ( - github.com/click33/sa-token-go/core v0.1.3 - github.com/click33/sa-token-go/storage/memory v0.1.3 - github.com/click33/sa-token-go/stputil v0.1.3 + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/storage/memory v0.1.8 + github.com/click33/sa-token-go/stputil v0.1.8 ) require ( diff --git a/go.work b/go.work index 5dd5f5477f4b0726897cf4a76b674c90afed9813..4fbc7f654b513043fb1dbd3f966d2c1ccf17e4b1 100644 --- a/go.work +++ b/go.work @@ -1,13 +1,18 @@ -go 1.25.3 +go 1.25.4 use ( ./core + ./examples/hertz/herz-example + ./examples/beego/beego-example ./examples/kratos/kratos-example + ./examples/multi-certification + ./integrations/beego ./integrations/chi ./integrations/echo ./integrations/fiber ./integrations/gf ./integrations/gin + ./integrations/hertz ./integrations/kratos ./storage/memory ./storage/redis diff --git a/go.work.sum b/go.work.sum index ff4fc5a1a8b169d815fc651e1bb960094b80b1cd..24d2af3b312e2754a49291370b0209d0f4e944c4 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,5 +1,6 @@ cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= @@ -8,34 +9,51 @@ cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4 cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0/go.mod h1:2bIszWvQRlJVmJLiuLhukLImRjKPcYdzzsx6darK02A= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/apache/thrift v0.13.0 h1:5hryIiq9gtn+MiLVn0wP37kb/uTeRZgN08WoCsAhIhI= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU= +github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bloom/v3 v3.5.0/go.mod h1:Y8vrn7nk1tPIlmLtW2ZPV+W7StdVMor6bC1xgpjMZFs= +github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/casbin/casbin v1.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/click33/sa-token-go/storage/memory v0.1.4/go.mod h1:nqyuEh23mNjcuG3aI/BqJFz71zkpsgjdStW1BC5lkB0= github.com/click33/sa-token-go/storage/memory v0.1.5/go.mod h1:HxN2NVLq7lx+sOmq5RmV0h8xJjEUJLm4Xt1Mq+9PV2s= +github.com/cloudflare/golz4 v0.0.0-20240916140612-caecf3c00c06/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk= github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY= github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySeApCX4GeOjPl9qhRF3QuIZq+Q= github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/couchbase/go-couchbase v0.1.1/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A= +github.com/couchbase/gomemcached v0.3.3/go.mod h1:pISAjweI42vljCumsJIo7CVhqIMIIP9g3Wfhl1JJw68= +github.com/couchbase/goutils v0.1.2/go.mod h1:h89Ek/tiOxxqjz30nPPlwZdQbdB8BwgnuBxeoUe/ViE= github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= +github.com/elastic/go-elasticsearch/v6 v6.8.10/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.11.2-0.20230627204322-7d0032219fcb h1:kxNVXsNro/lpR5WD+P1FI/yUHn2G03Glber3k8cQL2Y= github.com/envoyproxy/go-control-plane v0.11.2-0.20230627204322-7d0032219fcb/go.mod h1:GxGqnjWzl1Gz8WfAfMJSfhvsi4EPZayRb25nLHDWXyA= @@ -54,6 +72,10 @@ github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8 github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= +github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -64,9 +86,11 @@ github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvSc github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofiber/fiber/v2 v2.52.0/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/gogf/gf/v2 v2.9.4/go.mod h1:Ukl+5HUH9S7puBmNLR4L1zUqeRwi0nrW4OigOknEztU= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= @@ -76,29 +100,31 @@ github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1 h1:0pHpWtx9vcvC0xGZqEQlQdfSQs7WRlAjuPvk3fOZDCo= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik= github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4 h1:sIXJOMrYnQZJu7OB7ANSF4MYri2fTEGIsRLz6LwI4xE= @@ -109,58 +135,53 @@ github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stg github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo= github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 h1:LiZB1h0GIcudcDci2bxbqI6DXV8bF8POAnArqvRrIyw= github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig= github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/shirou/gopsutil/v3 v3.23.6 h1:5y46WPI9QBKBbK7EEccUPNXpJpNrvPuTD0O2zHEHT08= github.com/shirou/gopsutil/v3 v3.23.6/go.mod h1:j7QX50DrXYggrpN30W0Mo+I4/8U2UUIQrnrhqUeWrAU= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= +github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA= github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= +github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg= github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= @@ -168,15 +189,20 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +go.etcd.io/etcd/api/v3 v3.6.0/go.mod h1:Wt5yZqEmxgTNJGHob7mTVBJDZNXiHPtXTcPab37iFOw= +go.etcd.io/etcd/client/pkg/v3 v3.6.0/go.mod h1:Jv5SFWMnGvIBn8o3OaBq/PnT0jjsX8iNokAUessNjoA= +go.etcd.io/etcd/client/v3 v3.6.0/go.mod h1:Jzk/Knqe06pkOZPHXsQ0+vNDvMQrgIqJ0W8DwPdMJMg= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao= go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.11.2/go.mod h1:bx//lU66dPzNT+Y0hHA12ciKoMOH9iixEwCqC1OeQWQ= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= @@ -188,14 +214,16 @@ go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42s go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= @@ -203,11 +231,14 @@ golang.org/x/oauth2 v0.14.0 h1:P0Vrf/2538nmC0H+pEQ3MNFRRnVR7RlqyVw+bvm26z0= golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b h1:DU+gwOBXU+6bO0sEyO7o/NeMlxZxCZEvI7v+J4a1zRQ= @@ -216,16 +247,14 @@ golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= @@ -233,16 +262,19 @@ google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0 google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= +google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20/go.mod h1:Nr5H8+MlGWr5+xX/STzdoEqJrO+YteqFbMyCsrb6mH0= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= nullprogram.com/x/optparse v1.0.0 h1:xGFgVi5ZaWOnYdac2foDT3vg0ZZC9ErXFV57mr4OHrI= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/integrations/beego/annotation.go b/integrations/beego/annotation.go new file mode 100644 index 0000000000000000000000000000000000000000..a3080154d76c9335b06ef0fe883ad82b70011a3f --- /dev/null +++ b/integrations/beego/annotation.go @@ -0,0 +1,151 @@ +package beego + +import ( + "strings" + + "github.com/beego/beego/v2/server/web" + beegoctx "github.com/beego/beego/v2/server/web/context" + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/stputil" +) + +// Annotation constants | 注解常量 +const ( + TagSaCheckLogin = "sa_check_login" + TagSaCheckRole = "sa_check_role" + TagSaCheckPermission = "sa_check_permission" + TagSaCheckDisable = "sa_check_disable" + TagSaIgnore = "sa_ignore" +) + +// Annotation annotation structure | 注解结构体 +type Annotation struct { + CheckLogin bool `json:"checkLogin"` + CheckRole []string `json:"checkRole"` + CheckPermission []string `json:"checkPermission"` + CheckDisable bool `json:"checkDisable"` + Ignore bool `json:"ignore"` +} + +// Validate validates if annotation is valid | 验证注解是否有效 +func (a *Annotation) Validate() bool { + if a.Ignore { + return true + } + + count := 0 + if a.CheckLogin { + count++ + } + if len(a.CheckRole) > 0 { + count++ + } + if len(a.CheckPermission) > 0 { + count++ + } + if a.CheckDisable { + count++ + } + + return count <= 1 +} + +// GetHandler gets handler with annotations | 获取带注解的处理器 +func GetHandler(handler web.FilterFunc, annotations ...*Annotation) web.FilterFunc { + return func(ctx *beegoctx.Context) { + if len(annotations) > 0 && annotations[0].Ignore { + if handler != nil { + handler(ctx) + } + return + } + + bCtx := NewBeegoContext(ctx) + saCtx := core.NewContext(bCtx, stputil.GetManager()) + token := saCtx.GetTokenValue() + + if token == "" { + writeErrorResponse(ctx, core.NewNotLoginError()) + return + } + + if !stputil.IsLogin(token) { + writeErrorResponse(ctx, core.NewNotLoginError()) + return + } + + loginID, err := stputil.GetLoginID(token) + if err != nil { + writeErrorResponse(ctx, err) + return + } + + // Check if account is disabled | 检查是否被封禁 + if len(annotations) > 0 && annotations[0].CheckDisable { + if stputil.IsDisable(loginID) { + writeErrorResponse(ctx, core.NewAccountDisabledError(loginID)) + return + } + } + + // Check permission | 检查权限 + if len(annotations) > 0 && len(annotations[0].CheckPermission) > 0 { + hasPermission := false + for _, perm := range annotations[0].CheckPermission { + if stputil.HasPermission(loginID, strings.TrimSpace(perm)) { + hasPermission = true + break + } + } + if !hasPermission { + writeErrorResponse(ctx, core.NewPermissionDeniedError(strings.Join(annotations[0].CheckPermission, ","))) + return + } + } + + // Check role | 检查角色 + if len(annotations) > 0 && len(annotations[0].CheckRole) > 0 { + hasRole := false + for _, role := range annotations[0].CheckRole { + if stputil.HasRole(loginID, strings.TrimSpace(role)) { + hasRole = true + break + } + } + if !hasRole { + writeErrorResponse(ctx, core.NewRoleDeniedError(strings.Join(annotations[0].CheckRole, ","))) + return + } + } + + // All checks passed, execute original handler | 所有检查通过,执行原函数 + if handler != nil { + handler(ctx) + } + } +} + +// CheckLogin filter for login checking | 检查登录过滤器 +func CheckLogin() web.FilterFunc { + return GetHandler(nil, &Annotation{CheckLogin: true}) +} + +// CheckRole filter for role checking | 检查角色过滤器 +func CheckRole(roles ...string) web.FilterFunc { + return GetHandler(nil, &Annotation{CheckRole: roles}) +} + +// CheckPermission filter for permission checking | 检查权限过滤器 +func CheckPermission(perms ...string) web.FilterFunc { + return GetHandler(nil, &Annotation{CheckPermission: perms}) +} + +// CheckDisable filter for checking if account is disabled | 检查是否被封禁过滤器 +func CheckDisable() web.FilterFunc { + return GetHandler(nil, &Annotation{CheckDisable: true}) +} + +// Ignore filter to ignore authentication | 忽略认证过滤器 +func Ignore() web.FilterFunc { + return GetHandler(nil, &Annotation{Ignore: true}) +} diff --git a/integrations/beego/context.go b/integrations/beego/context.go new file mode 100644 index 0000000000000000000000000000000000000000..3e9ac03f1f0ff602a1d428b0dd775cb3faae08b9 --- /dev/null +++ b/integrations/beego/context.go @@ -0,0 +1,150 @@ +package beego + +import ( + beegoctx "github.com/beego/beego/v2/server/web/context" + "github.com/click33/sa-token-go/core/adapter" +) + +// BeegoContext Beego request context adapter | Beego请求上下文适配器 +type BeegoContext struct { + ctx *beegoctx.Context + aborted bool +} + +// NewBeegoContext creates a Beego context adapter | 创建Beego上下文适配器 +func NewBeegoContext(ctx *beegoctx.Context) adapter.RequestContext { + return &BeegoContext{ctx: ctx} +} + +// GetHeader gets request header | 获取请求头 +func (b *BeegoContext) GetHeader(key string) string { + return b.ctx.Input.Header(key) +} + +// GetQuery gets query parameter | 获取查询参数 +func (b *BeegoContext) GetQuery(key string) string { + return b.ctx.Input.Query(key) +} + +// GetCookie gets cookie | 获取Cookie +func (b *BeegoContext) GetCookie(key string) string { + return b.ctx.Input.Cookie(key) +} + +// SetHeader sets response header | 设置响应头 +func (b *BeegoContext) SetHeader(key, value string) { + b.ctx.ResponseWriter.Header().Set(key, value) +} + +// SetCookie sets cookie | 设置Cookie +func (b *BeegoContext) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) { + b.ctx.SetCookie(name, value, maxAge, path, domain, secure, httpOnly) +} + +// GetClientIP gets client IP address | 获取客户端IP地址 +func (b *BeegoContext) GetClientIP() string { + return b.ctx.Input.IP() +} + +// GetMethod gets request method | 获取请求方法 +func (b *BeegoContext) GetMethod() string { + return b.ctx.Input.Method() +} + +// GetPath gets request path | 获取请求路径 +func (b *BeegoContext) GetPath() string { + return b.ctx.Input.URI() +} + +// Set sets context value | 设置上下文值 +func (b *BeegoContext) Set(key string, value interface{}) { + b.ctx.Input.SetData(key, value) +} + +// Get gets context value | 获取上下文值 +func (b *BeegoContext) Get(key string) (interface{}, bool) { + value := b.ctx.Input.GetData(key) + return value, value != nil +} + +// ============ Additional Required Methods | 额外必需的方法 ============ + +// GetHeaders implements adapter.RequestContext. +func (b *BeegoContext) GetHeaders() map[string][]string { + headers := make(map[string][]string) + for key, values := range b.ctx.Request.Header { + headers[key] = values + } + return headers +} + +// GetQueryAll implements adapter.RequestContext. +func (b *BeegoContext) GetQueryAll() map[string][]string { + params := make(map[string][]string) + return params +} + +// GetPostForm implements adapter.RequestContext. +func (b *BeegoContext) GetPostForm(key string) string { + return b.ctx.Request.FormValue(key) +} + +// GetBody implements adapter.RequestContext. +func (b *BeegoContext) GetBody() ([]byte, error) { + return b.ctx.Input.RequestBody, nil +} + +// GetURL implements adapter.RequestContext. +func (b *BeegoContext) GetURL() string { + return b.ctx.Input.URL() +} + +// GetUserAgent implements adapter.RequestContext. +func (b *BeegoContext) GetUserAgent() string { + return b.ctx.Request.UserAgent() +} + +// SetCookieWithOptions implements adapter.RequestContext. +func (b *BeegoContext) SetCookieWithOptions(options *adapter.CookieOptions) { + b.ctx.SetCookie( + options.Name, + options.Value, + options.MaxAge, + options.Path, + options.Domain, + options.Secure, + options.HttpOnly, + ) +} + +// GetString implements adapter.RequestContext. +func (b *BeegoContext) GetString(key string) string { + value := b.ctx.Input.GetData(key) + if value == nil { + return "" + } + if str, ok := value.(string); ok { + return str + } + return "" +} + +// MustGet implements adapter.RequestContext. +func (b *BeegoContext) MustGet(key string) any { + value := b.ctx.Input.GetData(key) + if value == nil { + panic("key not found: " + key) + } + return value +} + +// Abort implements adapter.RequestContext. +func (b *BeegoContext) Abort() { + b.aborted = true + b.ctx.Abort(500, "aborted") // 必须选一个默认状态码和消息 +} + +// IsAborted implements adapter.RequestContext. +func (b *BeegoContext) IsAborted() bool { + return b.aborted +} diff --git a/integrations/beego/export.go b/integrations/beego/export.go new file mode 100644 index 0000000000000000000000000000000000000000..8fbb23e5787a1173240845a1143c62d67a067e60 --- /dev/null +++ b/integrations/beego/export.go @@ -0,0 +1,380 @@ +package beego + +import ( + "time" + + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/stputil" +) + +// ==============================类型别名 vs 显式包装函数========================================= +// 场景 推荐方式 +// 类型定义 type T = core.T(别名) +// 常量 const C = core.C +// 简单函数(无业务逻辑) 别名变量 var F = core.F(最简洁) +// 函数(需要文档/校验/未来扩展) 显式包装 func F(...) ... { return core.F(...) } +// API 一致性要求高 显式包装 +// ============================================================================================== + +// ============ Re-export core types | 重新导出核心类型 ============ + +// Configuration related types | 配置相关类型 +type ( + Config = core.Config + CookieConfig = core.CookieConfig + TokenStyle = core.TokenStyle +) + +// Token style constants | Token风格常量 +const ( + TokenStyleUUID = core.TokenStyleUUID + TokenStyleSimple = core.TokenStyleSimple + TokenStyleRandom32 = core.TokenStyleRandom32 + TokenStyleRandom64 = core.TokenStyleRandom64 + TokenStyleRandom128 = core.TokenStyleRandom128 + TokenStyleJWT = core.TokenStyleJWT + TokenStyleHash = core.TokenStyleHash + TokenStyleTimestamp = core.TokenStyleTimestamp + TokenStyleTik = core.TokenStyleTik +) + +// Core types | 核心类型 +type ( + Manager = core.Manager + TokenInfo = core.TokenInfo + Session = core.Session + TokenGenerator = core.TokenGenerator + SaTokenContext = core.SaTokenContext + Builder = core.Builder + NonceManager = core.NonceManager + RefreshTokenInfo = core.RefreshTokenInfo + RefreshTokenManager = core.RefreshTokenManager + OAuth2Server = core.OAuth2Server + OAuth2Client = core.OAuth2Client + OAuth2AccessToken = core.OAuth2AccessToken + OAuth2GrantType = core.OAuth2GrantType +) + +// Adapter interfaces | 适配器接口 +type ( + Storage = core.Storage + RequestContext = core.RequestContext +) + +// Event related types | 事件相关类型 +type ( + EventListener = core.EventListener + EventManager = core.EventManager + EventData = core.EventData + Event = core.Event + ListenerFunc = core.ListenerFunc + ListenerConfig = core.ListenerConfig +) + +// Event constants | 事件常量 +const ( + EventLogin = core.EventLogin + EventLogout = core.EventLogout + EventKickout = core.EventKickout + EventDisable = core.EventDisable + EventUntie = core.EventUntie + EventRenew = core.EventRenew + EventCreateSession = core.EventCreateSession + EventDestroySession = core.EventDestroySession + EventPermissionCheck = core.EventPermissionCheck + EventRoleCheck = core.EventRoleCheck + EventAll = core.EventAll +) + +// OAuth2 grant type constants | OAuth2授权类型常量 +const ( + GrantTypeAuthorizationCode = core.GrantTypeAuthorizationCode + GrantTypeRefreshToken = core.GrantTypeRefreshToken + GrantTypeClientCredentials = core.GrantTypeClientCredentials + GrantTypePassword = core.GrantTypePassword +) + +// Utility functions | 工具函数 +var ( + RandomString = core.RandomString + IsEmpty = core.IsEmpty + IsNotEmpty = core.IsNotEmpty + DefaultString = core.DefaultString + ContainsString = core.ContainsString + RemoveString = core.RemoveString + UniqueStrings = core.UniqueStrings + MergeStrings = core.MergeStrings + MatchPattern = core.MatchPattern +) + +// ============ Core constructor functions | 核心构造函数 ============ + +// DefaultConfig returns default configuration | 返回默认配置 +func DefaultConfig() *Config { + return core.DefaultConfig() +} + +// NewManager creates a new authentication manager | 创建新的认证管理器 +func NewManager(storage Storage, cfg *Config) *Manager { + return core.NewManager(storage, cfg) +} + +// NewContext creates a new Sa-Token context | 创建新的Sa-Token上下文 +func NewContext(ctx RequestContext, mgr *Manager) *SaTokenContext { + return core.NewContext(ctx, mgr) +} + +// NewSession creates a new session | 创建新的Session +func NewSession(id string, storage Storage, prefix string) *Session { + return core.NewSession(id, storage, prefix) +} + +// LoadSession loads an existing session | 加载已存在的Session +func LoadSession(id string, storage Storage, prefix string) (*Session, error) { + return core.LoadSession(id, storage, prefix) +} + +// NewTokenGenerator creates a new token generator | 创建新的Token生成器 +func NewTokenGenerator(cfg *Config) *TokenGenerator { + return core.NewTokenGenerator(cfg) +} + +// NewEventManager creates a new event manager | 创建新的事件管理器 +func NewEventManager() *EventManager { + return core.NewEventManager() +} + +// NewBuilder creates a new builder for fluent configuration | 创建新的Builder构建器(用于流式配置) +func NewBuilder() *Builder { + return core.NewBuilder() +} + +// NewNonceManager creates a new nonce manager | 创建新的Nonce管理器 +func NewNonceManager(storage Storage, prefix string, ttl ...int64) *NonceManager { + return core.NewNonceManager(storage, prefix, ttl...) +} + +// NewRefreshTokenManager creates a new refresh token manager | 创建新的刷新令牌管理器 +func NewRefreshTokenManager(storage Storage, prefix string, cfg *Config) *RefreshTokenManager { + return core.NewRefreshTokenManager(storage, prefix, cfg) +} + +// NewOAuth2Server creates a new OAuth2 server | 创建新的OAuth2服务器 +func NewOAuth2Server(storage Storage, prefix string) *OAuth2Server { + return core.NewOAuth2Server(storage, prefix) +} + +// ============ Global StpUtil functions | 全局StpUtil函数 ============ + +// SetManager sets the global Manager (must be called first) | 设置全局Manager(必须先调用此方法) +func SetManager(mgr *Manager) { + stputil.SetManager(mgr) +} + +// GetManager gets the global Manager | 获取全局Manager +func GetManager() *Manager { + return stputil.GetManager() +} + +// ============ Authentication | 登录认证 ============ + +// Login performs user login | 用户登录 +func Login(loginID interface{}, device ...string) (string, error) { + return stputil.Login(loginID, device...) +} + +// LoginByToken performs login with specified token | 使用指定Token登录 +func LoginByToken(loginID interface{}, tokenValue string, device ...string) error { + return stputil.LoginByToken(loginID, tokenValue, device...) +} + +// Logout performs user logout | 用户登出 +func Logout(loginID interface{}, device ...string) error { + return stputil.Logout(loginID, device...) +} + +// LogoutByToken performs logout by token | 根据Token登出 +func LogoutByToken(tokenValue string) error { + return stputil.LogoutByToken(tokenValue) +} + +// IsLogin checks if the user is logged in | 检查用户是否已登录 +func IsLogin(tokenValue string) bool { + return stputil.IsLogin(tokenValue) +} + +// CheckLoginByToken checks login status (throws error if not logged in) | 检查登录状态(未登录抛出错误) +func CheckLoginByToken(tokenValue string) error { + return stputil.CheckLogin(tokenValue) +} + +// GetLoginID gets the login ID from token | 从Token获取登录ID +func GetLoginID(tokenValue string) (string, error) { + return stputil.GetLoginID(tokenValue) +} + +// GetLoginIDNotCheck gets login ID without checking | 获取登录ID(不检查) +func GetLoginIDNotCheck(tokenValue string) (string, error) { + return stputil.GetLoginIDNotCheck(tokenValue) +} + +// GetTokenValue gets the token value for a login ID | 获取登录ID对应的Token值 +func GetTokenValue(loginID interface{}, device ...string) (string, error) { + return stputil.GetTokenValue(loginID, device...) +} + +// GetTokenInfo gets token information | 获取Token信息 +func GetTokenInfo(tokenValue string) (*TokenInfo, error) { + return stputil.GetTokenInfo(tokenValue) +} + +// ============ Kickout | 踢人下线 ============ + +// Kickout kicks out a user session | 踢人下线 +func Kickout(loginID interface{}, device ...string) error { + return stputil.Kickout(loginID, device...) +} + +// ============ Account Disable | 账号封禁 ============ + +// Disable disables an account for specified duration | 封禁账号(指定时长) +func Disable(loginID interface{}, duration time.Duration) error { + return stputil.Disable(loginID, duration) +} + +// IsDisable checks if an account is disabled | 检查账号是否被封禁 +func IsDisable(loginID interface{}) bool { + return stputil.IsDisable(loginID) +} + +// CheckDisableByToken checks if account is disabled (throws error if disabled) | 检查Token对应账号是否被封禁(被封禁则抛出错误) +func CheckDisableByToken(tokenValue string) error { + return stputil.CheckDisable(tokenValue) +} + +// GetDisableTime gets remaining disabled time | 获取账号剩余封禁时间 +func GetDisableTime(loginID interface{}) (int64, error) { + return stputil.GetDisableTime(loginID) +} + +// Untie unties/unlocks an account | 解除账号封禁 +func Untie(loginID interface{}) error { + return stputil.Untie(loginID) +} + +// ============ Permission Check | 权限验证 ============ + +// CheckPermissionByToken checks if the token has specified permission | 检查Token是否拥有指定权限 +func CheckPermissionByToken(tokenValue string, permission string) error { + return stputil.CheckPermission(tokenValue, permission) +} + +// HasPermission checks if the account has specified permission (returns bool) | 检查账号是否拥有指定权限(返回布尔值) +func HasPermission(loginID interface{}, permission string) bool { + return stputil.HasPermission(loginID, permission) +} + +// SetPermissions sets permissions for a login ID | 设置用户权限 +func SetPermissions(loginID interface{}, permissions []string) error { + return stputil.SetPermissions(loginID, permissions) +} + +// CheckPermissionAndByToken checks if the token has all specified permissions (AND logic) | 检查Token是否拥有所有指定权限(AND逻辑) +func CheckPermissionAndByToken(tokenValue string, permissions []string) error { + return stputil.CheckPermissionAnd(tokenValue, permissions) +} + +// CheckPermissionOrByToken checks if the token has any of the specified permissions (OR logic) | 检查Token是否拥有指定权限中的任意一个(OR逻辑) +func CheckPermissionOrByToken(tokenValue string, permissions []string) error { + return stputil.CheckPermissionOr(tokenValue, permissions) +} + +// GetPermissionListByToken gets the permission list for a token | 获取Token的权限列表 +func GetPermissionListByToken(tokenValue string) ([]string, error) { + return stputil.GetPermissionList(tokenValue) +} + +// ============ Role Check | 角色验证 ============ + +// CheckRoleByToken checks if the token has specified role | 检查Token是否拥有指定角色 +func CheckRoleByToken(tokenValue string, role string) error { + return stputil.CheckRole(tokenValue, role) +} + +// HasRole checks if the account has specified role (returns bool) | 检查账号是否拥有指定角色(返回布尔值) +func HasRole(loginID interface{}, role string) bool { + return stputil.HasRole(loginID, role) +} + +// SetRoles sets roles for a login ID | 设置用户角色 +func SetRoles(loginID interface{}, roles []string) error { + return stputil.SetRoles(loginID, roles) +} + +// CheckRoleAndByToken checks if the token has all specified roles (AND logic) | 检查Token是否拥有所有指定角色(AND逻辑) +func CheckRoleAndByToken(tokenValue string, roles []string) error { + return stputil.CheckRoleAnd(tokenValue, roles) +} + +// CheckRoleOrByToken checks if the token has any of the specified roles (OR logic) | 检查Token是否拥有指定角色中的任意一个(OR逻辑) +func CheckRoleOrByToken(tokenValue string, roles []string) error { + return stputil.CheckRoleOr(tokenValue, roles) +} + +// GetRoleListByToken gets the role list for a token | 获取Token的角色列表 +func GetRoleListByToken(tokenValue string) ([]string, error) { + return stputil.GetRoleList(tokenValue) +} + +// ============ Session Management | Session管理 ============ + +// GetSession gets the session for a login ID | 获取登录ID的Session +func GetSession(loginID interface{}) (*Session, error) { + return stputil.GetSession(loginID) +} + +// GetSessionByToken gets the session by token | 根据Token获取Session +func GetSessionByToken(tokenValue string) (*Session, error) { + return stputil.GetSessionByToken(tokenValue) +} + +// GetTokenSession gets the token session | 获取Token的Session +func GetTokenSession(tokenValue string) (*Session, error) { + return stputil.GetTokenSession(tokenValue) +} + +// ============ Security Features | 安全特性 ============ + +// GenerateNonce generates a new nonce token | 生成新的Nonce令牌 +func GenerateNonce() (string, error) { + return stputil.GenerateNonce() +} + +// VerifyNonce verifies a nonce token | 验证Nonce令牌 +func VerifyNonce(nonce string) bool { + return stputil.VerifyNonce(nonce) +} + +// LoginWithRefreshToken performs login and returns refresh token info | 登录并返回刷新令牌信息 +func LoginWithRefreshToken(loginID interface{}, device ...string) (*RefreshTokenInfo, error) { + return stputil.LoginWithRefreshToken(loginID, device...) +} + +// RefreshAccessToken refreshes the access token using a refresh token | 使用刷新令牌刷新访问令牌 +func RefreshAccessToken(refreshToken string) (*RefreshTokenInfo, error) { + return stputil.RefreshAccessToken(refreshToken) +} + +// RevokeRefreshToken revokes a refresh token | 撤销刷新令牌 +func RevokeRefreshToken(refreshToken string) error { + return stputil.RevokeRefreshToken(refreshToken) +} + +// GetOAuth2Server gets the OAuth2 server instance | 获取OAuth2服务器实例 +func GetOAuth2Server() *OAuth2Server { + return stputil.GetOAuth2Server() +} + +// 字符串常量,没有包装必要 +// Version Sa-Token-Go version | Sa-Token-Go版本 +const Version = core.Version diff --git a/integrations/beego/go.mod b/integrations/beego/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..a2a3fc66e985d3a56f0a9705692b37b24926770d --- /dev/null +++ b/integrations/beego/go.mod @@ -0,0 +1,39 @@ +module github.com/click33/sa-token-go/integrations/beego + +go 1.24.2 + +require ( + github.com/beego/beego/v2 v2.3.10 + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/stputil v0.1.8 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/click33/sa-token-go/storage/memory v0.1.8 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/panjf2000/ants/v2 v2.11.3 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.63.0 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02 // indirect + golang.org/x/crypto v0.38.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace ( + github.com/click33/sa-token-go/core => ../../core + github.com/click33/sa-token-go/stputil => ../../stputil +) diff --git a/integrations/beego/go.sum b/integrations/beego/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..2b14b0afe985025f0072ddb198b843a6720e255b --- /dev/null +++ b/integrations/beego/go.sum @@ -0,0 +1,68 @@ +github.com/beego/beego/v2 v2.3.10 h1:53us+Lzc/bwFwjyRi62+FKFEcWiIbmKbhW4/FQ3aNEw= +github.com/beego/beego/v2 v2.3.10/go.mod h1:IY3bkfRge4Yj2XhgdMuvznM4HK/ovxQLOHJxAJ+QRgo= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/click33/sa-token-go/storage/memory v0.1.8 h1:kyhSFWBfmO8XNXye+E9d4gw0bJE2HePwWg5xmkE2rFw= +github.com/click33/sa-token-go/storage/memory v0.1.8/go.mod h1:cPQUpY0D/3lGyCyzW/lZ4pc+O9HPfYV3FLeWABpmPnk= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw= +github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg= +github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= +github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02 h1:v9ezJDHA1XGxViAUSIoO/Id7Fl63u6d0YmsAm+/p2hs= +github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02/go.mod h1:RF16/A3L0xSa0oSERcnhd8Pu3IXSDZSK2gmGIMsttFE= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/integrations/beego/plugin.go b/integrations/beego/plugin.go new file mode 100644 index 0000000000000000000000000000000000000000..caab60f0c61b10fa627f8dd3c371623c4e2dfd2a --- /dev/null +++ b/integrations/beego/plugin.go @@ -0,0 +1,260 @@ +package beego + +import ( + "encoding/json" + "errors" + "net/http" + + "github.com/beego/beego/v2/server/web" + beegoctx "github.com/beego/beego/v2/server/web/context" + "github.com/click33/sa-token-go/core" +) + +// Plugin Beego plugin for Sa-Token | Beego插件 +type Plugin struct { + manager *core.Manager +} + +// NewPlugin creates a Beego plugin | 创建Beego插件 +func NewPlugin(manager *core.Manager) *Plugin { + return &Plugin{ + manager: manager, + } +} + +// AuthMiddleware authentication middleware | 认证中间件 +func (p *Plugin) AuthMiddleware() web.FilterFunc { + return func(ctx *beegoctx.Context) { + bCtx := NewBeegoContext(ctx) + saCtx := core.NewContext(bCtx, p.manager) + + if err := saCtx.CheckLogin(); err != nil { + writeErrorResponse(ctx, err) + return + } + + ctx.Input.SetData("satoken", saCtx) + } +} + +// PathAuthMiddleware path-based authentication middleware | 基于路径的鉴权中间件 +func (p *Plugin) PathAuthMiddleware(config *core.PathAuthConfig) web.FilterFunc { + return func(ctx *beegoctx.Context) { + path := ctx.Input.URI() + token := ctx.Input.Header(p.manager.GetConfig().TokenName) + if token == "" { + token = ctx.Input.Cookie(p.manager.GetConfig().TokenName) + } + + result := core.ProcessAuth(path, token, config, p.manager) + + if result.ShouldReject() { + writeErrorResponse(ctx, core.NewPathAuthRequiredError(path)) + return + } + + if result.IsValid && result.TokenInfo != nil { + bCtx := NewBeegoContext(ctx) + saCtx := core.NewContext(bCtx, p.manager) + ctx.Input.SetData("satoken", saCtx) + ctx.Input.SetData("loginID", result.LoginID()) + } + } +} + +// PermissionRequired permission validation middleware | 权限验证中间件 +func (p *Plugin) PermissionRequired(permission string) web.FilterFunc { + return func(ctx *beegoctx.Context) { + bCtx := NewBeegoContext(ctx) + saCtx := core.NewContext(bCtx, p.manager) + + if err := saCtx.CheckLogin(); err != nil { + writeErrorResponse(ctx, err) + return + } + + if !saCtx.HasPermission(permission) { + writeErrorResponse(ctx, core.NewPermissionDeniedError(permission)) + return + } + + ctx.Input.SetData("satoken", saCtx) + } +} + +// RoleRequired role validation middleware | 角色验证中间件 +func (p *Plugin) RoleRequired(role string) web.FilterFunc { + return func(ctx *beegoctx.Context) { + bCtx := NewBeegoContext(ctx) + saCtx := core.NewContext(bCtx, p.manager) + + if err := saCtx.CheckLogin(); err != nil { + writeErrorResponse(ctx, err) + return + } + + if !saCtx.HasRole(role) { + writeErrorResponse(ctx, core.NewRoleDeniedError(role)) + return + } + + ctx.Input.SetData("satoken", saCtx) + } +} + +// LoginHandler login handler | 登录处理器 +func (p *Plugin) LoginHandler(ctx *beegoctx.Context) { + var req struct { + Username string `json:"username"` + Password string `json:"password"` + Device string `json:"device"` + } + + // Get request body - beego v2 may need CopyBody to populate RequestBody + body := ctx.Input.RequestBody + if len(body) == 0 { + body = ctx.Input.CopyBody(1024 << 10) // 1MB max + } + + if err := json.Unmarshal(body, &req); err != nil { + writeErrorResponse(ctx, core.NewError(core.CodeBadRequest, "invalid request parameters", err)) + return + } + + device := req.Device + if device == "" { + device = "default" + } + + token, err := p.manager.Login(req.Username, device) + if err != nil { + writeErrorResponse(ctx, core.NewError(core.CodeServerError, "login failed", err)) + return + } + + cfg := p.manager.GetConfig() + if cfg.IsReadCookie { + maxAge := max(int(cfg.Timeout), 0) + ctx.SetCookie( + cfg.TokenName, + token, + maxAge, + cfg.CookieConfig.Path, + cfg.CookieConfig.Domain, + cfg.CookieConfig.Secure, + cfg.CookieConfig.HttpOnly, + ) + } + + writeSuccessResponse(ctx, map[string]interface{}{ + "token": token, + }) +} + +// LogoutHandler logout handler | 登出处理器 +func (p *Plugin) LogoutHandler(ctx *beegoctx.Context) { + bCtx := NewBeegoContext(ctx) + saCtx := core.NewContext(bCtx, p.manager) + + loginID, err := saCtx.GetLoginID() + if err != nil { + writeErrorResponse(ctx, err) + return + } + + if err := p.manager.Logout(loginID); err != nil { + writeErrorResponse(ctx, core.NewError(core.CodeServerError, "logout failed", err)) + return + } + + writeSuccessResponse(ctx, map[string]interface{}{ + "message": "logout successful", + }) +} + +// UserInfoHandler user info handler | 获取用户信息处理器 +func (p *Plugin) UserInfoHandler(ctx *beegoctx.Context) { + bCtx := NewBeegoContext(ctx) + saCtx := core.NewContext(bCtx, p.manager) + + loginID, err := saCtx.GetLoginID() + if err != nil { + writeErrorResponse(ctx, err) + return + } + + permissions, _ := p.manager.GetPermissions(loginID) + roles, _ := p.manager.GetRoles(loginID) + + writeSuccessResponse(ctx, map[string]interface{}{ + "loginId": loginID, + "permissions": permissions, + "roles": roles, + }) +} + +// GetSaToken gets Sa-Token context from Beego context | 从Beego上下文获取Sa-Token上下文 +func GetSaToken(ctx *beegoctx.Context) (*core.SaTokenContext, bool) { + satoken := ctx.Input.GetData("satoken") + if satoken == nil { + return nil, false + } + saCtx, ok := satoken.(*core.SaTokenContext) + return saCtx, ok +} + +// ============ Error Handling Helpers | 错误处理辅助函数 ============ + +// writeErrorResponse writes a standardized error response | 写入标准化的错误响应 +func writeErrorResponse(ctx *beegoctx.Context, err error) error { + var saErr *core.SaTokenError + var code int + var message string + var httpStatus int + + if errors.As(err, &saErr) { + code = saErr.Code + message = saErr.Message + httpStatus = getHTTPStatusFromCode(code) + } else { + code = core.CodeServerError + message = err.Error() + httpStatus = http.StatusInternalServerError + } + + ctx.ResponseWriter.WriteHeader(httpStatus) + ctx.ResponseWriter.Header().Set("Content-Type", "application/json") + return json.NewEncoder(ctx.ResponseWriter).Encode(map[string]interface{}{ + "code": code, + "message": message, + "error": err.Error(), + }) +} + +// writeSuccessResponse writes a standardized success response | 写入标准化的成功响应 +func writeSuccessResponse(ctx *beegoctx.Context, data interface{}) error { + ctx.ResponseWriter.Header().Set("Content-Type", "application/json") + return json.NewEncoder(ctx.ResponseWriter).Encode(map[string]interface{}{ + "code": core.CodeSuccess, + "message": "success", + "data": data, + }) +} + +// getHTTPStatusFromCode converts Sa-Token error code to HTTP status | 将Sa-Token错误码转换为HTTP状态码 +func getHTTPStatusFromCode(code int) int { + switch code { + case core.CodeNotLogin: + return http.StatusUnauthorized + case core.CodePermissionDenied: + return http.StatusForbidden + case core.CodeBadRequest: + return http.StatusBadRequest + case core.CodeNotFound: + return http.StatusNotFound + case core.CodeServerError: + return http.StatusInternalServerError + default: + return http.StatusInternalServerError + } +} diff --git a/integrations/chi/annotation.go b/integrations/chi/annotation.go index b8ec1d9d7ebf2a413e5786d458f4191bda14b2c4..ffb0374fc5562b332b99d8aedbfe53924b22987b 100644 --- a/integrations/chi/annotation.go +++ b/integrations/chi/annotation.go @@ -20,7 +20,6 @@ type Annotation struct { // GetHandler gets handler with annotations | 获取带注解的处理器 func GetHandler(handler http.Handler, annotations ...*Annotation) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Check if authentication should be ignored | 检查是否忽略认证 if len(annotations) > 0 && annotations[0].Ignore { if handler != nil { handler.ServeHTTP(w, r) @@ -28,22 +27,20 @@ func GetHandler(handler http.Handler, annotations ...*Annotation) http.Handler { return } - // Get token from context using configured TokenName | 从上下文获取Token(使用配置的TokenName) ctx := NewChiContext(w, r) saCtx := core.NewContext(ctx, stputil.GetManager()) token := saCtx.GetTokenValue() + if token == "" { writeErrorResponse(w, core.NewNotLoginError()) return } - // Check login | 检查登录 if !stputil.IsLogin(token) { writeErrorResponse(w, core.NewNotLoginError()) return } - // Get login ID | 获取登录ID loginID, err := stputil.GetLoginID(token) if err != nil { writeErrorResponse(w, err) diff --git a/integrations/chi/go.mod b/integrations/chi/go.mod index 7ef3a25e7d7f25c015237c4d0e9e45372de142d1..a01e54731ab9ebae918ce7c6c8311be20f61f8b9 100644 --- a/integrations/chi/go.mod +++ b/integrations/chi/go.mod @@ -3,8 +3,8 @@ module github.com/click33/sa-token-go/integrations/chi go 1.23.0 require ( - github.com/click33/sa-token-go/core v0.1.5 - github.com/click33/sa-token-go/stputil v0.1.5 + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/stputil v0.1.8 ) require ( diff --git a/integrations/chi/plugin.go b/integrations/chi/plugin.go index 7fdab881ea728fb0fa44ff14e8a7d6e4bb030931..33f6e3896748c6609005350b78512257416ede5b 100644 --- a/integrations/chi/plugin.go +++ b/integrations/chi/plugin.go @@ -39,6 +39,38 @@ func (p *Plugin) AuthMiddleware() func(http.Handler) http.Handler { } } +// PathAuthMiddleware path-based authentication middleware | 基于路径的鉴权中间件 +func (p *Plugin) PathAuthMiddleware(config *core.PathAuthConfig) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + path := r.URL.Path + token := r.Header.Get(p.manager.GetConfig().TokenName) + if token == "" { + cookie, _ := r.Cookie(p.manager.GetConfig().TokenName) + if cookie != nil { + token = cookie.Value + } + } + + result := core.ProcessAuth(path, token, config, p.manager) + + if result.ShouldReject() { + writeErrorResponse(w, core.NewPathAuthRequiredError(path)) + return + } + + if result.IsValid && result.TokenInfo != nil { + ctx := NewChiContext(w, r) + saCtx := core.NewContext(ctx, p.manager) + ctx.Set("satoken", saCtx) + ctx.Set("loginID", result.LoginID()) + } + + next.ServeHTTP(w, r) + }) + } +} + // PermissionRequired permission validation middleware | 权限验证中间件 func (p *Plugin) PermissionRequired(permission string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { diff --git a/integrations/echo/annotation.go b/integrations/echo/annotation.go index a8c980426aa25f1a6ce4474a5d7f466f8f3031ce..804471b0e8406a5a9ecd2012df5810cd04013a69 100644 --- a/integrations/echo/annotation.go +++ b/integrations/echo/annotation.go @@ -20,7 +20,6 @@ type Annotation struct { // GetHandler gets handler with annotations | 获取带注解的处理器 func GetHandler(handler echo.HandlerFunc, annotations ...*Annotation) echo.HandlerFunc { return func(c echo.Context) error { - // Check if authentication should be ignored | 检查是否忽略认证 if len(annotations) > 0 && annotations[0].Ignore { if handler != nil { return handler(c) @@ -28,20 +27,18 @@ func GetHandler(handler echo.HandlerFunc, annotations ...*Annotation) echo.Handl return nil } - // Get token from context using configured TokenName | 从上下文获取Token(使用配置的TokenName) ctx := NewEchoContext(c) saCtx := core.NewContext(ctx, stputil.GetManager()) token := saCtx.GetTokenValue() + if token == "" { return writeErrorResponse(c, core.NewNotLoginError()) } - // Check login | 检查登录 if !stputil.IsLogin(token) { return writeErrorResponse(c, core.NewNotLoginError()) } - // Get login ID | 获取登录ID loginID, err := stputil.GetLoginID(token) if err != nil { return writeErrorResponse(c, err) diff --git a/integrations/echo/go.mod b/integrations/echo/go.mod index ae7ee59fe8221397280408e9a7cdc93f0d475b79..f2074975674968cce0ac18698ca914c286306a3f 100644 --- a/integrations/echo/go.mod +++ b/integrations/echo/go.mod @@ -5,8 +5,8 @@ go 1.23.0 toolchain go1.24.1 require ( - github.com/click33/sa-token-go/core v0.1.5 - github.com/click33/sa-token-go/stputil v0.1.5 + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/stputil v0.1.8 github.com/labstack/echo/v4 v4.11.4 ) diff --git a/integrations/echo/plugin.go b/integrations/echo/plugin.go index 50299c8d2e54ad2c0bebbee9bf44ae798de0c692..35fe69bda876e00354c31e38529ede20879c2409 100644 --- a/integrations/echo/plugin.go +++ b/integrations/echo/plugin.go @@ -37,6 +37,37 @@ func (p *Plugin) AuthMiddleware() echo.MiddlewareFunc { } } +// PathAuthMiddleware path-based authentication middleware | 基于路径的鉴权中间件 +func (p *Plugin) PathAuthMiddleware(config *core.PathAuthConfig) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + path := c.Request().URL.Path + token := c.Request().Header.Get(p.manager.GetConfig().TokenName) + if token == "" { + cookie, _ := c.Cookie(p.manager.GetConfig().TokenName) + if cookie != nil { + token = cookie.Value + } + } + + result := core.ProcessAuth(path, token, config, p.manager) + + if result.ShouldReject() { + return writeErrorResponse(c, core.NewPathAuthRequiredError(path)) + } + + if result.IsValid && result.TokenInfo != nil { + ctx := NewEchoContext(c) + saCtx := core.NewContext(ctx, p.manager) + c.Set("satoken", saCtx) + c.Set("loginID", result.LoginID()) + } + + return next(c) + } + } +} + // PermissionRequired permission validation middleware | 权限验证中间件 func (p *Plugin) PermissionRequired(permission string) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { diff --git a/integrations/fiber/annotation.go b/integrations/fiber/annotation.go index 0182710f83324e7e9416fa319d87ddebb1c59949..ed3ca6280502bca131e47017f85df78c73b5b96e 100644 --- a/integrations/fiber/annotation.go +++ b/integrations/fiber/annotation.go @@ -20,7 +20,6 @@ type Annotation struct { // GetHandler gets handler with annotations | 获取带注解的处理器 func GetHandler(handler fiber.Handler, annotations ...*Annotation) fiber.Handler { return func(c *fiber.Ctx) error { - // Check if authentication should be ignored | 检查是否忽略认证 if len(annotations) > 0 && annotations[0].Ignore { if handler != nil { return handler(c) @@ -28,20 +27,18 @@ func GetHandler(handler fiber.Handler, annotations ...*Annotation) fiber.Handler return c.Next() } - // Get token from context using configured TokenName | 从上下文获取Token(使用配置的TokenName) ctx := NewFiberContext(c) saCtx := core.NewContext(ctx, stputil.GetManager()) token := saCtx.GetTokenValue() + if token == "" { return writeErrorResponse(c, core.NewNotLoginError()) } - // Check login | 检查登录 if !stputil.IsLogin(token) { return writeErrorResponse(c, core.NewNotLoginError()) } - // Get login ID | 获取登录ID loginID, err := stputil.GetLoginID(token) if err != nil { return writeErrorResponse(c, err) diff --git a/integrations/fiber/go.mod b/integrations/fiber/go.mod index abb41150d1392918c4f286cca011c815b3aa898b..ae840ab98cbdee9fa47e8fc3c82baeb9fdbe28d5 100644 --- a/integrations/fiber/go.mod +++ b/integrations/fiber/go.mod @@ -5,8 +5,8 @@ go 1.23.0 toolchain go1.24.1 require ( - github.com/click33/sa-token-go/core v0.1.5 - github.com/click33/sa-token-go/stputil v0.1.5 + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/stputil v0.1.8 github.com/gofiber/fiber/v2 v2.52.0 ) diff --git a/integrations/fiber/plugin.go b/integrations/fiber/plugin.go index 7aa193b66f9d1a0e4f6fbc93be19f3bb8863f0e6..cf2e128570d968639ee8bd2f9e11a6cb742eb4c7 100644 --- a/integrations/fiber/plugin.go +++ b/integrations/fiber/plugin.go @@ -34,6 +34,32 @@ func (p *Plugin) AuthMiddleware() fiber.Handler { } } +// PathAuthMiddleware path-based authentication middleware | 基于路径的鉴权中间件 +func (p *Plugin) PathAuthMiddleware(config *core.PathAuthConfig) fiber.Handler { + return func(c *fiber.Ctx) error { + path := c.Path() + token := c.Get(p.manager.GetConfig().TokenName) + if token == "" { + token = c.Cookies(p.manager.GetConfig().TokenName) + } + + result := core.ProcessAuth(path, token, config, p.manager) + + if result.ShouldReject() { + return writeErrorResponse(c, core.NewPathAuthRequiredError(path)) + } + + if result.IsValid && result.TokenInfo != nil { + ctx := NewFiberContext(c) + saCtx := core.NewContext(ctx, p.manager) + c.Locals("satoken", saCtx) + c.Locals("loginID", result.LoginID()) + } + + return c.Next() + } +} + // PermissionRequired permission validation middleware | 权限验证中间件 func (p *Plugin) PermissionRequired(permission string) fiber.Handler { return func(c *fiber.Ctx) error { diff --git a/integrations/gf/annotation.go b/integrations/gf/annotation.go index d1e06300eabe449585c151c3d01e3a81fba88408..379a2738ba8c1b54a5ba4fc04762efd897ede555 100644 --- a/integrations/gf/annotation.go +++ b/integrations/gf/annotation.go @@ -20,7 +20,6 @@ type Annotation struct { // GetHandler gets handler with annotations | 获取带注解的处理器 func GetHandler(handler ghttp.HandlerFunc, annotations ...*Annotation) ghttp.HandlerFunc { return func(r *ghttp.Request) { - // Check if authentication should be ignored | 检查是否忽略认证 if len(annotations) > 0 && annotations[0].Ignore { if handler != nil { handler(r) @@ -30,22 +29,20 @@ func GetHandler(handler ghttp.HandlerFunc, annotations ...*Annotation) ghttp.Han return } - // Get token from context using configured TokenName | 从上下文获取Token(使用配置的TokenName) ctx := NewGFContext(r) saCtx := core.NewContext(ctx, stputil.GetManager()) token := saCtx.GetTokenValue() + if token == "" { writeErrorResponse(r, core.NewNotLoginError()) return } - // Check login | 检查登录 if !stputil.IsLogin(token) { writeErrorResponse(r, core.NewNotLoginError()) return } - // Get login ID | 获取登录ID loginID, err := stputil.GetLoginID(token) if err != nil { writeErrorResponse(r, err) diff --git a/integrations/gf/go.mod b/integrations/gf/go.mod index 340f21fe8224b6c1a66a58ed0134328170668894..d7c283332e9c545e951157d26a82c1554e999666 100644 --- a/integrations/gf/go.mod +++ b/integrations/gf/go.mod @@ -3,8 +3,8 @@ module github.com/click33/sa-token-go/integrations/gf go 1.24.1 require ( - github.com/click33/sa-token-go/core v0.1.5 - github.com/click33/sa-token-go/stputil v0.1.5 + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/stputil v0.1.8 github.com/gogf/gf/v2 v2.9.4 ) diff --git a/integrations/gf/plugin.go b/integrations/gf/plugin.go index d9d18db4171a2a53d21901ee10112b27e9e1dc9c..78b51e76da8cf8317163a60e098e73b131346954 100644 --- a/integrations/gf/plugin.go +++ b/integrations/gf/plugin.go @@ -47,6 +47,33 @@ func (p *Plugin) AuthMiddleware() ghttp.HandlerFunc { } +// PathAuthMiddleware path-based authentication middleware | 基于路径的鉴权中间件 +func (p *Plugin) PathAuthMiddleware(config *core.PathAuthConfig) ghttp.HandlerFunc { + return func(r *ghttp.Request) { + path := r.Request.URL.Path + token := r.Header.Get(p.manager.GetConfig().TokenName) + if token == "" { + token = r.Cookie.Get(p.manager.GetConfig().TokenName).String() + } + + result := core.ProcessAuth(path, token, config, p.manager) + + if result.ShouldReject() { + writeErrorResponse(r, core.NewPathAuthRequiredError(path)) + return + } + + if result.IsValid && result.TokenInfo != nil { + ctx := NewGFContext(r) + saCtx := core.NewContext(ctx, p.manager) + r.SetCtxVar("satoken", saCtx) + r.SetCtxVar("loginID", result.LoginID()) + } + + r.Middleware.Next() + } +} + // PermissionRequired permission validation middleware | 权限验证中间件 func (p *Plugin) PermissionRequired(permission string) ghttp.HandlerFunc { return func(r *ghttp.Request) { diff --git a/integrations/gin/annotation.go b/integrations/gin/annotation.go index cc7a1dc0f6a992e9ef3d865ba3d677b621992b04..3b8d4f439ffe028d297c9848a23f042d53f0a7c5 100644 --- a/integrations/gin/annotation.go +++ b/integrations/gin/annotation.go @@ -90,7 +90,6 @@ func (a *Annotation) Validate() bool { // GetHandler gets handler with annotations | 获取带注解的处理器 func GetHandler(handler interface{}, annotations ...*Annotation) ginfw.HandlerFunc { return func(c *ginfw.Context) { - // Check if authentication should be ignored | 检查是否忽略认证 if len(annotations) > 0 && annotations[0].Ignore { if callHandler(handler, c) { return @@ -99,24 +98,22 @@ func GetHandler(handler interface{}, annotations ...*Annotation) ginfw.HandlerFu return } - // Get token from context using configured TokenName | 从上下文获取Token(使用配置的TokenName) ctx := NewGinContext(c) saCtx := core.NewContext(ctx, stputil.GetManager()) token := saCtx.GetTokenValue() + if token == "" { writeErrorResponse(c, core.NewNotLoginError()) c.Abort() return } - // Check login | 检查登录 if !stputil.IsLogin(token) { writeErrorResponse(c, core.NewNotLoginError()) c.Abort() return } - // Get login ID | 获取登录ID loginID, err := stputil.GetLoginID(token) if err != nil { writeErrorResponse(c, err) @@ -289,31 +286,27 @@ func (h *HandlerWithAnnotations) ToGinHandler() ginfw.HandlerFunc { // Middleware 创建中间件版本 func Middleware(annotations ...*Annotation) ginfw.HandlerFunc { return func(c *ginfw.Context) { - - // 检查是否忽略认证 if len(annotations) > 0 && annotations[0].Ignore { c.Next() return } - // 获取Token(使用配置的TokenName) ctx := NewGinContext(c) saCtx := core.NewContext(ctx, stputil.GetManager()) token := saCtx.GetTokenValue() + if token == "" { writeErrorResponse(c, core.NewNotLoginError()) c.Abort() return } - // 检查登录 if !stputil.IsLogin(token) { writeErrorResponse(c, core.NewNotLoginError()) c.Abort() return } - // 获取登录ID loginID, err := stputil.GetLoginID(token) if err != nil { writeErrorResponse(c, err) diff --git a/integrations/gin/go.mod b/integrations/gin/go.mod index 3b8d3d3611500829144ac3ed02718aad904665a5..36448dc709d5403ebcbb40a7bb5e2c970d0d306f 100644 --- a/integrations/gin/go.mod +++ b/integrations/gin/go.mod @@ -5,8 +5,8 @@ go 1.23.0 toolchain go1.24.1 require ( - github.com/click33/sa-token-go/core v0.1.5 - github.com/click33/sa-token-go/stputil v0.1.5 + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/stputil v0.1.8 github.com/gin-gonic/gin v1.10.0 github.com/stretchr/testify v1.11.1 ) diff --git a/integrations/gin/plugin.go b/integrations/gin/plugin.go index d5e3f292af1611feae048aa42d505793cab7d094..8d084f71484083c09cafe60db5428c200ef011ea 100644 --- a/integrations/gin/plugin.go +++ b/integrations/gin/plugin.go @@ -39,6 +39,34 @@ func (p *Plugin) AuthMiddleware() gin.HandlerFunc { } } +// PathAuthMiddleware path-based authentication middleware | 基于路径的鉴权中间件 +func (p *Plugin) PathAuthMiddleware(config *core.PathAuthConfig) gin.HandlerFunc { + return func(c *gin.Context) { + path := c.Request.URL.Path + token := c.GetHeader(p.manager.GetConfig().TokenName) + if token == "" { + token, _ = c.Cookie(p.manager.GetConfig().TokenName) + } + + result := core.ProcessAuth(path, token, config, p.manager) + + if result.ShouldReject() { + writeErrorResponse(c, core.NewPathAuthRequiredError(path)) + c.Abort() + return + } + + if result.IsValid && result.TokenInfo != nil { + ctx := NewGinContext(c) + saCtx := core.NewContext(ctx, p.manager) + c.Set("satoken", saCtx) + c.Set("loginID", result.LoginID()) + } + + c.Next() + } +} + // PermissionRequired permission validation middleware | 权限验证中间件 func (p *Plugin) PermissionRequired(permission string) gin.HandlerFunc { return func(c *gin.Context) { diff --git a/integrations/hertz/annotation.go b/integrations/hertz/annotation.go new file mode 100644 index 0000000000000000000000000000000000000000..158a6fe8ef8c2f387e5e3ac6d6169762039f598f --- /dev/null +++ b/integrations/hertz/annotation.go @@ -0,0 +1,362 @@ +package hertz + +import ( + "context" + "reflect" + "strings" + + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/stputil" + "github.com/cloudwego/hertz/pkg/app" +) + +// Annotation constants | 注解常量 +const ( + TagSaCheckLogin = "sa_check_login" + TagSaCheckRole = "sa_check_role" + TagSaCheckPermission = "sa_check_permission" + TagSaCheckDisable = "sa_check_disable" + TagSaIgnore = "sa_ignore" +) + +// Annotation annotation structure | 注解结构体 +type Annotation struct { + CheckLogin bool `json:"checkLogin"` + CheckRole []string `json:"checkRole"` + CheckPermission []string `json:"checkPermission"` + CheckDisable bool `json:"checkDisable"` + Ignore bool `json:"ignore"` +} + +// ParseTag parses struct tags | 解析结构体标签 +func ParseTag(tag string) *Annotation { + ann := &Annotation{} + + if tag == "" { + return ann + } + + parts := strings.Split(tag, ",") + for _, part := range parts { + part = strings.TrimSpace(part) + switch { + case part == TagSaCheckLogin || part == "login": + ann.CheckLogin = true + case strings.HasPrefix(part, TagSaCheckRole+"=") || strings.HasPrefix(part, "role="): + roles := strings.TrimPrefix(part, TagSaCheckRole+"=") + roles = strings.TrimPrefix(roles, "role=") + if roles != "" { + ann.CheckRole = strings.Split(roles, "|") + } + case strings.HasPrefix(part, TagSaCheckPermission+"=") || strings.HasPrefix(part, "permission="): + perms := strings.TrimPrefix(part, TagSaCheckPermission+"=") + perms = strings.TrimPrefix(perms, "permission=") + if perms != "" { + ann.CheckPermission = strings.Split(perms, "|") + } + case part == TagSaCheckDisable || part == "disable": + ann.CheckDisable = true + case part == TagSaIgnore || part == "ignore": + ann.Ignore = true + } + } + + return ann +} + +// Validate validates if annotation is valid | 验证注解是否有效 +func (a *Annotation) Validate() bool { + if a.Ignore { + return true // When ignore is true, other checks are invalid | 忽略认证时,其他检查无效 + } + + count := 0 + if a.CheckLogin { + count++ + } + if len(a.CheckRole) > 0 { + count++ + } + if len(a.CheckPermission) > 0 { + count++ + } + if a.CheckDisable { + count++ + } + + // At most one check type allowed | 最多只能有一个检查类型 + return count <= 1 +} + +// GetHandler gets handler with annotations | 获取带注解的处理器 +func GetHandler(handler interface{}, annotations ...*Annotation) app.HandlerFunc { + return func(ctx context.Context, c *app.RequestContext) { + if len(annotations) > 0 && annotations[0].Ignore { + if callHandler(handler, ctx, c) { + return + } + c.Next(ctx) + return + } + + hCtx := NewHertzContext(c) + saCtx := core.NewContext(hCtx, stputil.GetManager()) + token := saCtx.GetTokenValue() + + if token == "" { + writeErrorResponse(c, core.NewNotLoginError()) + c.Abort() + return + } + + if !stputil.IsLogin(token) { + writeErrorResponse(c, core.NewNotLoginError()) + c.Abort() + return + } + + loginID, err := stputil.GetLoginID(token) + if err != nil { + writeErrorResponse(c, err) + c.Abort() + return + } + + // Check if account is disabled | 检查是否被封禁 + if len(annotations) > 0 && annotations[0].CheckDisable { + if stputil.IsDisable(loginID) { + writeErrorResponse(c, core.NewAccountDisabledError(loginID)) + c.Abort() + return + } + } + + // Check permission | 检查权限 + if len(annotations) > 0 && len(annotations[0].CheckPermission) > 0 { + hasPermission := false + for _, perm := range annotations[0].CheckPermission { + if stputil.HasPermission(loginID, strings.TrimSpace(perm)) { + hasPermission = true + break + } + } + if !hasPermission { + writeErrorResponse(c, core.NewPermissionDeniedError(strings.Join(annotations[0].CheckPermission, ","))) + c.Abort() + return + } + } + + // Check role | 检查角色 + if len(annotations) > 0 && len(annotations[0].CheckRole) > 0 { + hasRole := false + for _, role := range annotations[0].CheckRole { + if stputil.HasRole(loginID, strings.TrimSpace(role)) { + hasRole = true + break + } + } + if !hasRole { + writeErrorResponse(c, core.NewRoleDeniedError(strings.Join(annotations[0].CheckRole, ","))) + c.Abort() + return + } + } + + // All checks passed, execute original handler or continue | 所有检查通过,执行原函数或继续 + if callHandler(handler, ctx, c) { + return + } + c.Next(ctx) + } +} + +func callHandler(handler interface{}, ctx context.Context, c *app.RequestContext) bool { + if handler == nil { + return false + } + + switch h := handler.(type) { + case func(*app.RequestContext): + if h == nil { + return false + } + h(c) + return true + case app.HandlerFunc: + if h == nil { + return false + } + h(ctx, c) + return true + } + + hv := reflect.ValueOf(handler) + if hv.Kind() != reflect.Func || hv.IsNil() || hv.Type().NumIn() != 1 { + return false + } + + argType := hv.Type().In(0) + if !argType.AssignableTo(reflect.TypeOf(c)) { + return false + } + + hv.Call([]reflect.Value{reflect.ValueOf(c)}) + return true +} + +// Decorator functions | 装饰器函数 + +// CheckLogin decorator for login checking | 检查登录装饰器 +func CheckLogin() app.HandlerFunc { + return GetHandler(nil, &Annotation{CheckLogin: true}) +} + +// CheckRole decorator for role checking | 检查角色装饰器 +func CheckRole(roles ...string) app.HandlerFunc { + return GetHandler(nil, &Annotation{CheckRole: roles}) +} + +// CheckPermission decorator for permission checking | 检查权限装饰器 +func CheckPermission(perms ...string) app.HandlerFunc { + return GetHandler(nil, &Annotation{CheckPermission: perms}) +} + +// CheckDisable decorator for checking if account is disabled | 检查是否被封禁装饰器 +func CheckDisable() app.HandlerFunc { + return GetHandler(nil, &Annotation{CheckDisable: true}) +} + +// Ignore decorator to ignore authentication | 忽略认证装饰器 +func Ignore() app.HandlerFunc { + return GetHandler(nil, &Annotation{Ignore: true}) +} + +// WithAnnotation decorator with custom annotation | 使用自定义注解装饰器 +func WithAnnotation(ann *Annotation) app.HandlerFunc { + return GetHandler(nil, ann) +} + +// ProcessStructAnnotations processes annotations on struct tags | 处理结构体上的注解标签 +func ProcessStructAnnotations(handler interface{}) app.HandlerFunc { + handlerValue := reflect.ValueOf(handler) + handlerType := reflect.TypeOf(handler) + + // Find method name, usually the last path segment | 查找方法名,通常是最后一个路径段 + methodName := "unknown" + if handlerType.Kind() == reflect.Ptr { + handlerType = handlerType.Elem() + } + if handlerType.Kind() == reflect.Struct { + methodName = handlerType.Name() + } + + // Parse method annotations | 解析方法上的注解标签 + ann := parseMethodAnnotation(handlerType, methodName) + + return GetHandler(func(c *app.RequestContext) { + handlerValue.MethodByName("ServeHTTP").Call([]reflect.Value{reflect.ValueOf(c)}) + }, ann) +} + +// parseMethodAnnotation parses method annotations | 解析方法注解 +func parseMethodAnnotation(t reflect.Type, methodName string) *Annotation { + // Simplified implementation, returns empty annotation | 简化实现,直接返回空注解 + return &Annotation{} +} + +// HandlerWithAnnotations 带注解的处理器包装器 +type HandlerWithAnnotations struct { + Handler interface{} + Annotations []*Annotation +} + +// NewHandlerWithAnnotations 创建带注解的处理器 +func NewHandlerWithAnnotations(handler interface{}, annotations ...*Annotation) *HandlerWithAnnotations { + return &HandlerWithAnnotations{ + Handler: handler, + Annotations: annotations, + } +} + +// ToGinHandler 转换为Gin处理器 +func (h *HandlerWithAnnotations) ToGinHandler() app.HandlerFunc { + return GetHandler(h.Handler, h.Annotations...) +} + +// Middleware 创建中间件版本 +func Middleware(annotations ...*Annotation) app.HandlerFunc { + return func(ctx context.Context, c *app.RequestContext) { + if len(annotations) > 0 && annotations[0].Ignore { + c.Next(ctx) + return + } + + hCtx := NewHertzContext(c) + saCtx := core.NewContext(hCtx, stputil.GetManager()) + token := saCtx.GetTokenValue() + + if token == "" { + writeErrorResponse(c, core.NewNotLoginError()) + c.Abort() + return + } + + if !stputil.IsLogin(token) { + writeErrorResponse(c, core.NewNotLoginError()) + c.Abort() + return + } + + loginID, err := stputil.GetLoginID(token) + if err != nil { + writeErrorResponse(c, err) + c.Abort() + return + } + + // 检查是否被封禁 + if len(annotations) > 0 && annotations[0].CheckDisable { + if stputil.IsDisable(loginID) { + writeErrorResponse(c, core.NewAccountDisabledError(loginID)) + c.Abort() + return + } + } + + // 检查权限 + if len(annotations) > 0 && len(annotations[0].CheckPermission) > 0 { + hasPermission := false + for _, perm := range annotations[0].CheckPermission { + if stputil.HasPermission(loginID, strings.TrimSpace(perm)) { + hasPermission = true + break + } + } + if !hasPermission { + writeErrorResponse(c, core.NewPermissionDeniedError(strings.Join(annotations[0].CheckPermission, ","))) + c.Abort() + return + } + } + + // 检查角色 + if len(annotations) > 0 && len(annotations[0].CheckRole) > 0 { + hasRole := false + for _, role := range annotations[0].CheckRole { + if stputil.HasRole(loginID, strings.TrimSpace(role)) { + hasRole = true + break + } + } + if !hasRole { + writeErrorResponse(c, core.NewRoleDeniedError(strings.Join(annotations[0].CheckRole, ","))) + c.Abort() + return + } + } + + // 所有检查通过,继续下一个处理器 + c.Next(ctx) + } +} diff --git a/integrations/hertz/annotation_test.go b/integrations/hertz/annotation_test.go new file mode 100644 index 0000000000000000000000000000000000000000..9d2f6575bc9fd73722fc2bc04160d6eaeac6586f --- /dev/null +++ b/integrations/hertz/annotation_test.go @@ -0,0 +1,457 @@ +package hertz + +import ( + "context" + "net/http" + "testing" + + "github.com/click33/sa-token-go/core/config" + "github.com/click33/sa-token-go/core/manager" + "github.com/click33/sa-token-go/storage/memory" + "github.com/click33/sa-token-go/stputil" + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/app/server" + "github.com/cloudwego/hertz/pkg/common/ut" + "github.com/cloudwego/hertz/pkg/common/utils" + "github.com/stretchr/testify/assert" +) + +// setupTestRouter 创建测试路由器和初始化 sa-token +func setupTestRouter() *server.Hertz { + router := server.Default() + + // 创建内存存储 + storage := memory.NewStorage() + + // 创建配置(必须开启 IsReadHeader 才能从 Authorization 头读取 token) + cfg := &config.Config{ + TokenName: "satoken", + Timeout: 2592000, // 30 天(秒) + IsConcurrent: true, + IsShare: true, + MaxLoginCount: -1, + IsReadHeader: true, + } + + // 创建并设置全局 Manager + mgr := manager.NewManager(storage, cfg) + stputil.SetManager(mgr) + + return router +} + +// mockLogin 模拟用户登录并返回 token +func mockLogin(loginID interface{}) string { + token, _ := stputil.Login(loginID) + return token +} + +// mockLoginWithRole 模拟用户登录并设置角色 +func mockLoginWithRole(loginID interface{}, roles []string) string { + token, _ := stputil.Login(loginID) + stputil.SetRoles(loginID, roles) + return token +} + +// mockLoginWithPermission 模拟用户登录并设置权限 +func mockLoginWithPermission(loginID interface{}, permissions []string) string { + token, _ := stputil.Login(loginID) + stputil.SetPermissions(loginID, permissions) + return token +} + +// TestCheckRole_WithValidRole 测试具有有效角色的用户访问 +func TestCheckRole_WithValidRole(t *testing.T) { + router := setupTestRouter() + + // 设置路由 - 使用 CheckRole 作为中间件 + router.GET("/admin", CheckRole("Admin"), func(ctx context.Context, c *app.RequestContext) { + c.JSON(http.StatusOK, utils.H{"message": "success"}) + }) + + // 创建一个具有 Admin 角色的用户 + token := mockLoginWithRole("user123", []string{"Admin"}) + // 发送请求 + w := ut.PerformRequest(router.Engine, "GET", "/admin", nil, + ut.Header{Key: "Authorization", Value: token}) + + // 断言 + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "success") +} + +// TestCheckRole_WithInvalidRole 测试没有所需角色的用户访问 +func TestCheckRole_WithInvalidRole(t *testing.T) { + router := setupTestRouter() + + // 设置路由 + router.GET("/admin", CheckRole("Admin"), func(ctx context.Context, c *app.RequestContext) { + c.JSON(http.StatusOK, utils.H{"message": "success"}) + }) + + // 创建一个只有 User 角色的用户 + token := mockLoginWithRole("user456", []string{"User"}) + + // 发送请求 + w := ut.PerformRequest(router.Engine, "GET", "/admin", nil, + ut.Header{Key: "Authorization", Value: token}) + + // 断言 + assert.Equal(t, http.StatusForbidden, w.Code) + assert.Contains(t, w.Body.String(), "role denied") +} + +// TestCheckRole_MultipleRoles 测试多个角色的情况(OR 逻辑) +func TestCheckRole_MultipleRoles(t *testing.T) { + router := setupTestRouter() + + // 设置路由 - 需要 Admin 或 SuperAdmin 角色 + router.GET("/manage", CheckRole("Admin", "SuperAdmin"), func(ctx context.Context, c *app.RequestContext) { + c.JSON(http.StatusOK, utils.H{"message": "success"}) + }) + + // 测试具有 SuperAdmin 角色的用户 + token := mockLoginWithRole("superuser", []string{"SuperAdmin"}) + + w := ut.PerformRequest(router.Engine, "GET", "/manage", nil, + ut.Header{Key: "Authorization", Value: token}) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "success") +} + +// TestCheckRole_NoToken 测试未提供 token 的情况 +func TestCheckRole_NoToken(t *testing.T) { + router := setupTestRouter() + + router.GET("/admin", CheckRole("Admin"), func(ctx context.Context, c *app.RequestContext) { + c.JSON(http.StatusOK, utils.H{"message": "success"}) + }) + + w := ut.PerformRequest(router.Engine, "GET", "/admin", nil) + + assert.Equal(t, http.StatusUnauthorized, w.Code) + assert.Contains(t, w.Body.String(), "user not logged in") +} + +// TestCheckRole_InvalidToken 测试无效 token 的情况 +func TestCheckRole_InvalidToken(t *testing.T) { + router := setupTestRouter() + + router.GET("/admin", CheckRole("Admin"), func(ctx context.Context, c *app.RequestContext) { + c.JSON(http.StatusOK, utils.H{"message": "success"}) + }) + + w := ut.PerformRequest(router.Engine, "GET", "/admin", nil, + ut.Header{Key: "Authorization", Value: "invalid-token-12345"}) + + assert.Equal(t, http.StatusUnauthorized, w.Code) + assert.Contains(t, w.Body.String(), "user not logged in") +} + +// TestCheckPermission_WithValidPermission 测试具有有效权限的用户访问 +func TestCheckPermission_WithValidPermission(t *testing.T) { + router := setupTestRouter() + + router.GET("/users", CheckPermission("user.read"), func(ctx context.Context, c *app.RequestContext) { + c.JSON(http.StatusOK, utils.H{"message": "success"}) + }) + + token := mockLoginWithPermission("user789", []string{"user.read"}) + + w := ut.PerformRequest(router.Engine, "GET", "/users", nil, + ut.Header{Key: "Authorization", Value: token}) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "success") +} + +// TestCheckPermission_WithInvalidPermission 测试没有所需权限的用户访问 +func TestCheckPermission_WithInvalidPermission(t *testing.T) { + router := setupTestRouter() + + router.GET("/users", CheckPermission("user.delete"), func(ctx context.Context, c *app.RequestContext) { + c.JSON(http.StatusOK, utils.H{"message": "success"}) + }) + + token := mockLoginWithPermission("user789", []string{"user.read"}) + + w := ut.PerformRequest(router.Engine, "GET", "/users", nil, + ut.Header{Key: "Authorization", Value: token}) + + assert.Equal(t, http.StatusForbidden, w.Code) + assert.Contains(t, w.Body.String(), "permission denied") +} + +// TestCheckLogin_Success 测试登录检查成功 +func TestCheckLogin_Success(t *testing.T) { + router := setupTestRouter() + + router.GET("/profile", CheckLogin(), func(ctx context.Context, c *app.RequestContext) { + c.JSON(http.StatusOK, utils.H{"message": "profile data"}) + }) + + token := mockLogin("user999") + + w := ut.PerformRequest(router.Engine, "GET", "/profile", nil, + ut.Header{Key: "Authorization", Value: token}) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "profile data") +} + +// TestCheckLogin_Failed 测试登录检查失败 +func TestCheckLogin_Failed(t *testing.T) { + router := setupTestRouter() + + router.GET("/profile", CheckLogin(), func(ctx context.Context, c *app.RequestContext) { + c.JSON(http.StatusOK, utils.H{"message": "profile data"}) + }) + + w := ut.PerformRequest(router.Engine, "GET", "/profile", nil) + + assert.Equal(t, http.StatusUnauthorized, w.Code) + assert.Contains(t, w.Body.String(), "user not logged in") +} + +// TestCheckDisable_NotDisabled 测试账号未被封禁的情况 +func TestCheckDisable_NotDisabled(t *testing.T) { + router := setupTestRouter() + + router.GET("/resource", CheckDisable(), func(ctx context.Context, c *app.RequestContext) { + c.JSON(http.StatusOK, utils.H{"message": "resource data"}) + }) + + token := mockLogin("user101") + + w := ut.PerformRequest(router.Engine, "GET", "/resource", nil, + ut.Header{Key: "Authorization", Value: token}) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "resource data") +} + +// TestCheckDisable_IsDisabled 测试账号被封禁的情况 +// 注意:core 的 Disable() 会踢出该账号所有 token,封禁后原 token 已失效,故请求返回 401 而非 403 +func TestCheckDisable_IsDisabled(t *testing.T) { + router := setupTestRouter() + + router.GET("/resource", CheckDisable(), func(ctx context.Context, c *app.RequestContext) { + c.JSON(http.StatusOK, utils.H{"message": "resource data"}) + }) + + loginID := "user102" + token := mockLogin(loginID) + + // 封禁账号(会同时踢出该账号所有 token) + stputil.Disable(loginID, 3600) // 封禁 1 小时 + + w := ut.PerformRequest(router.Engine, "GET", "/resource", nil, + ut.Header{Key: "Authorization", Value: token}) + + // 封禁后 token 已被踢下线,请求得到 401 + assert.Equal(t, http.StatusUnauthorized, w.Code) + assert.Contains(t, w.Body.String(), "user not logged in") +} + +// TestIgnore_SkipsAuthentication 测试忽略认证装饰器 +func TestIgnore_SkipsAuthentication(t *testing.T) { + router := setupTestRouter() + + router.GET("/public", Ignore(), func(ctx context.Context, c *app.RequestContext) { + c.JSON(http.StatusOK, utils.H{"message": "public data"}) + }) + + w := ut.PerformRequest(router.Engine, "GET", "/public", nil) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "public data") +} + +// TestChainedMiddleware_CheckRoleAndHandler 测试链式中间件:CheckRole + 实际处理器 +func TestChainedMiddleware_CheckRoleAndHandler(t *testing.T) { + router := setupTestRouter() + + // 模拟用户示例代码的使用方式 + safeGroup := router.Group("/safe") + { + safeGroup.GET("", CheckRole("SuperAdmin"), func(ctx context.Context, c *app.RequestContext) { + c.JSON(http.StatusOK, utils.H{"message": "safe settings"}) + }) + } + + // 测试具有 SuperAdmin 角色的用户 + token := mockLoginWithRole("admin123", []string{"SuperAdmin"}) + + w := ut.PerformRequest(router.Engine, "GET", "/safe", nil, + ut.Header{Key: "Authorization", Value: token}) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "safe settings") +} + +// TestChainedMiddleware_CheckRoleAndHandler_NoRole 测试链式中间件:无角色访问 +func TestChainedMiddleware_CheckRoleAndHandler_NoRole(t *testing.T) { + router := setupTestRouter() + + safeGroup := router.Group("/safe") + { + safeGroup.GET("", CheckRole("SuperAdmin"), func(ctx context.Context, c *app.RequestContext) { + c.JSON(http.StatusOK, utils.H{"message": "safe settings"}) + }) + } + + // 测试具有普通用户角色 + token := mockLoginWithRole("user123", []string{"User"}) + + w := ut.PerformRequest(router.Engine, "GET", "/safe", nil, + ut.Header{Key: "Authorization", Value: token}) + + assert.Equal(t, http.StatusForbidden, w.Code) + assert.Contains(t, w.Body.String(), "role denied") +} + +// TestGetHandler_WithNilHandler 测试 GetHandler 在 handler 为 nil 时的行为 +func TestGetHandler_WithNilHandler(t *testing.T) { + router := setupTestRouter() + + // 直接使用 GetHandler 创建中间件 + middleware := GetHandler(nil, &Annotation{CheckRole: []string{"Admin"}}) + + router.GET("/test", middleware, func(ctx context.Context, c *app.RequestContext) { + c.JSON(http.StatusOK, utils.H{"message": "test passed"}) + }) + + token := mockLoginWithRole("testuser", []string{"Admin"}) + + w := ut.PerformRequest(router.Engine, "GET", "/test", nil, + ut.Header{Key: "Authorization", Value: token}) + + // 应该能够正常执行,不会 panic + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "test passed") +} + +// TestMiddleware_CheckRole 测试 Middleware 函数的角色检查 +func TestMiddleware_CheckRole(t *testing.T) { + router := setupTestRouter() + + // 使用 Middleware 函数 + router.GET("/api/data", Middleware(&Annotation{CheckRole: []string{"Admin"}}), func(ctx context.Context, c *app.RequestContext) { + c.JSON(http.StatusOK, utils.H{"data": "sensitive data"}) + }) + + token := mockLoginWithRole("admin999", []string{"Admin"}) + + w := ut.PerformRequest(router.Engine, "GET", "/api/data", nil, + ut.Header{Key: "Authorization", Value: token}) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Contains(t, w.Body.String(), "sensitive data") +} + +// TestParseTag 测试标签解析功能 +func TestParseTag(t *testing.T) { + tests := []struct { + name string + tag string + expected *Annotation + }{ + { + name: "解析登录检查标签", + tag: "sa_check_login", + expected: &Annotation{ + CheckLogin: true, + }, + }, + { + name: "解析角色检查标签", + tag: "sa_check_role=Admin|SuperAdmin", + expected: &Annotation{ + CheckRole: []string{"Admin", "SuperAdmin"}, + }, + }, + { + name: "解析权限检查标签", + tag: "sa_check_permission=user.read|user.write", + expected: &Annotation{ + CheckPermission: []string{"user.read", "user.write"}, + }, + }, + { + name: "解析忽略认证标签", + tag: "sa_ignore", + expected: &Annotation{ + Ignore: true, + }, + }, + { + name: "解析封禁检查标签", + tag: "sa_check_disable", + expected: &Annotation{ + CheckDisable: true, + }, + }, + { + name: "空标签", + tag: "", + expected: &Annotation{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ParseTag(tt.tag) + assert.Equal(t, tt.expected.CheckLogin, result.CheckLogin) + assert.Equal(t, tt.expected.CheckRole, result.CheckRole) + assert.Equal(t, tt.expected.CheckPermission, result.CheckPermission) + assert.Equal(t, tt.expected.CheckDisable, result.CheckDisable) + assert.Equal(t, tt.expected.Ignore, result.Ignore) + }) + } +} + +// TestAnnotationValidate 测试注解验证功能 +func TestAnnotationValidate(t *testing.T) { + tests := []struct { + name string + annotation *Annotation + valid bool + }{ + { + name: "有效的单一检查", + annotation: &Annotation{ + CheckLogin: true, + }, + valid: true, + }, + { + name: "有效的忽略标记", + annotation: &Annotation{ + Ignore: true, + CheckLogin: true, // 即使有其他标记,忽略时仍然有效 + }, + valid: true, + }, + { + name: "有效的空注解", + annotation: &Annotation{}, + valid: true, + }, + { + name: "无效的多重检查", + annotation: &Annotation{ + CheckLogin: true, + CheckRole: []string{"Admin"}, + }, + valid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.annotation.Validate() + assert.Equal(t, tt.valid, result) + }) + } +} diff --git a/integrations/hertz/context.go b/integrations/hertz/context.go new file mode 100644 index 0000000000000000000000000000000000000000..2b3a29b5ba49464347b1ac38a1047417af16d0d4 --- /dev/null +++ b/integrations/hertz/context.go @@ -0,0 +1,165 @@ +package hertz + +import ( + "github.com/click33/sa-token-go/core/adapter" + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/protocol" +) + +// HertzContext Hertz request context adapter | Hertz 请求上下文适配器 +type HertzContext struct { + c *app.RequestContext + aborted bool +} + +// NewHertzContext creates a Hertz context adapter | 创建Hertz上下文适配器 +func NewHertzContext(c *app.RequestContext) adapter.RequestContext { + return &HertzContext{c: c} +} + +// GetHeader gets request header | 获取请求头 +func (h *HertzContext) GetHeader(key string) string { + return string(h.c.GetHeader(key)) +} + +// GetQuery gets query parameter | 获取查询参数 +func (h *HertzContext) GetQuery(key string) string { + return h.c.Query(key) +} + +// GetCookie gets cookie | 获取Cookie +func (h *HertzContext) GetCookie(key string) string { + cookie := h.c.Cookie(key) + return string(cookie) +} + +// SetHeader sets response header | 设置响应头 +func (h *HertzContext) SetHeader(key, value string) { + h.c.Header(key, value) +} + +// SetCookie sets cookie | 设置Cookie +func (h *HertzContext) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) { + h.c.SetCookie(name, value, maxAge, path, domain, protocol.CookieSameSiteLaxMode, secure, httpOnly) +} + +// GetClientIP gets client IP address | 获取客户端IP地址 +func (h *HertzContext) GetClientIP() string { + return h.c.ClientIP() +} + +// GetMethod gets request method | 获取请求方法 +func (h *HertzContext) GetMethod() string { + return string(h.c.Method()) +} + +// GetPath gets request path | 获取请求路径 +func (h *HertzContext) GetPath() string { + return string(h.c.Path()) +} + +// Set sets context value | 设置上下文值 +func (h *HertzContext) Set(key string, value interface{}) { + h.c.Set(key, value) +} + +// Get gets context value | 获取上下文值 +func (h *HertzContext) Get(key string) (interface{}, bool) { + return h.c.Get(key) +} + +// ============ Additional Required Methods | 额外必需的方法 ============ + +// GetHeaders implements adapter.RequestContext. +func (h *HertzContext) GetHeaders() map[string][]string { + headers := make(map[string][]string) + h.c.Request.Header.VisitAll(func(key, value []byte) { + headers[string(key)] = []string{string(value)} + }) + return headers +} + +// GetQueryAll implements adapter.RequestContext. +func (h *HertzContext) GetQueryAll() map[string][]string { + params := make(map[string][]string) + h.c.QueryArgs().VisitAll(func(key, value []byte) { + params[string(key)] = []string{string(value)} + }) + return params +} + +// GetPostForm implements adapter.RequestContext. +func (h *HertzContext) GetPostForm(key string) string { + return h.c.PostForm(key) +} + +// GetBody implements adapter.RequestContext. +func (h *HertzContext) GetBody() ([]byte, error) { + return h.c.GetRawData(), nil +} + +// GetURL implements adapter.RequestContext. +func (h *HertzContext) GetURL() string { + return string(h.c.Request.URI().RequestURI()) +} + +// GetUserAgent implements adapter.RequestContext. +func (h *HertzContext) GetUserAgent() string { + return string(h.c.UserAgent()) +} + +// SetCookieWithOptions implements adapter.RequestContext. +func (h *HertzContext) SetCookieWithOptions(options *adapter.CookieOptions) { + // Set SameSite attribute + var sameSite protocol.CookieSameSite + switch options.SameSite { + case "Strict": + sameSite = protocol.CookieSameSiteStrictMode + case "Lax": + sameSite = protocol.CookieSameSiteLaxMode + case "None": + sameSite = protocol.CookieSameSiteNoneMode + } + h.c.SetCookie( + options.Name, + options.Value, + options.MaxAge, + options.Path, + options.Domain, + sameSite, + options.Secure, + options.HttpOnly, + ) +} + +// GetString implements adapter.RequestContext. +func (h *HertzContext) GetString(key string) string { + value, exists := h.c.Get(key) + if !exists { + return "" + } + if str, ok := value.(string); ok { + return str + } + return "" +} + +// MustGet implements adapter.RequestContext. +func (h *HertzContext) MustGet(key string) any { + value, exists := h.c.Get(key) + if !exists { + panic("key not found: " + key) + } + return value +} + +// Abort implements adapter.RequestContext. +func (h *HertzContext) Abort() { + h.aborted = true + h.c.Abort() +} + +// IsAborted implements adapter.RequestContext. +func (h *HertzContext) IsAborted() bool { + return h.aborted +} diff --git a/integrations/hertz/export.go b/integrations/hertz/export.go new file mode 100644 index 0000000000000000000000000000000000000000..ee2241a4fb2c9cc8b97b6a56cd8b290eb90e5603 --- /dev/null +++ b/integrations/hertz/export.go @@ -0,0 +1,364 @@ +package hertz + +import ( + "time" + + "github.com/click33/sa-token-go/core" + "github.com/click33/sa-token-go/stputil" +) + +// ============ Re-export core types | 重新导出核心类型 ============ + +// Configuration related types | 配置相关类型 +type ( + Config = core.Config + CookieConfig = core.CookieConfig + TokenStyle = core.TokenStyle +) + +// Token style constants | Token风格常量 +const ( + TokenStyleUUID = core.TokenStyleUUID + TokenStyleSimple = core.TokenStyleSimple + TokenStyleRandom32 = core.TokenStyleRandom32 + TokenStyleRandom64 = core.TokenStyleRandom64 + TokenStyleRandom128 = core.TokenStyleRandom128 + TokenStyleJWT = core.TokenStyleJWT + TokenStyleHash = core.TokenStyleHash + TokenStyleTimestamp = core.TokenStyleTimestamp + TokenStyleTik = core.TokenStyleTik +) + +// Core types | 核心类型 +type ( + Manager = core.Manager + TokenInfo = core.TokenInfo + Session = core.Session + TokenGenerator = core.TokenGenerator + SaTokenContext = core.SaTokenContext + Builder = core.Builder + NonceManager = core.NonceManager + RefreshTokenInfo = core.RefreshTokenInfo + RefreshTokenManager = core.RefreshTokenManager + OAuth2Server = core.OAuth2Server + OAuth2Client = core.OAuth2Client + OAuth2AccessToken = core.OAuth2AccessToken + OAuth2GrantType = core.OAuth2GrantType +) + +// Adapter interfaces | 适配器接口 +type ( + Storage = core.Storage + RequestContext = core.RequestContext +) + +// Event related types | 事件相关类型 +type ( + EventListener = core.EventListener + EventManager = core.EventManager + EventData = core.EventData + Event = core.Event + ListenerFunc = core.ListenerFunc + ListenerConfig = core.ListenerConfig +) + +// Event constants | 事件常量 +const ( + EventLogin = core.EventLogin + EventLogout = core.EventLogout + EventKickout = core.EventKickout + EventDisable = core.EventDisable + EventUntie = core.EventUntie + EventRenew = core.EventRenew + EventCreateSession = core.EventCreateSession + EventDestroySession = core.EventDestroySession + EventPermissionCheck = core.EventPermissionCheck + EventRoleCheck = core.EventRoleCheck + EventAll = core.EventAll +) + +// OAuth2 grant type constants | OAuth2授权类型常量 +const ( + GrantTypeAuthorizationCode = core.GrantTypeAuthorizationCode + GrantTypeRefreshToken = core.GrantTypeRefreshToken + GrantTypeClientCredentials = core.GrantTypeClientCredentials + GrantTypePassword = core.GrantTypePassword +) + +// Utility functions | 工具函数 +var ( + RandomString = core.RandomString + IsEmpty = core.IsEmpty + IsNotEmpty = core.IsNotEmpty + DefaultString = core.DefaultString + ContainsString = core.ContainsString + RemoveString = core.RemoveString + UniqueStrings = core.UniqueStrings + MergeStrings = core.MergeStrings + MatchPattern = core.MatchPattern +) + +// ============ Core constructor functions | 核心构造函数 ============ + +// DefaultConfig returns default configuration | 返回默认配置 +func DefaultConfig() *Config { + return core.DefaultConfig() +} + +// NewManager creates a new authentication manager | 创建新的认证管理器 +func NewManager(storage Storage, cfg *Config) *Manager { + return core.NewManager(storage, cfg) +} + +// NewContext creates a new Sa-Token context | 创建新的Sa-Token上下文 +func NewContext(ctx RequestContext, mgr *Manager) *SaTokenContext { + return core.NewContext(ctx, mgr) +} + +// NewSession creates a new session | 创建新的Session +func NewSession(id string, storage Storage, prefix string) *Session { + return core.NewSession(id, storage, prefix) +} + +// LoadSession loads an existing session | 加载已存在的Session +func LoadSession(id string, storage Storage, prefix string) (*Session, error) { + return core.LoadSession(id, storage, prefix) +} + +// NewTokenGenerator creates a new token generator | 创建新的Token生成器 +func NewTokenGenerator(cfg *Config) *TokenGenerator { + return core.NewTokenGenerator(cfg) +} + +// NewEventManager creates a new event manager | 创建新的事件管理器 +func NewEventManager() *EventManager { + return core.NewEventManager() +} + +// NewBuilder creates a new builder for fluent configuration | 创建新的Builder构建器(用于流式配置) +func NewBuilder() *Builder { + return core.NewBuilder() +} + +// NewNonceManager creates a new nonce manager | 创建新的Nonce管理器 +func NewNonceManager(storage Storage, prefix string, ttl ...int64) *NonceManager { + return core.NewNonceManager(storage, prefix, ttl...) +} + +// NewRefreshTokenManager creates a new refresh token manager | 创建新的刷新令牌管理器 +func NewRefreshTokenManager(storage Storage, prefix string, cfg *Config) *RefreshTokenManager { + return core.NewRefreshTokenManager(storage, prefix, cfg) +} + +// NewOAuth2Server creates a new OAuth2 server | 创建新的OAuth2服务器 +func NewOAuth2Server(storage Storage, prefix string) *OAuth2Server { + return core.NewOAuth2Server(storage, prefix) +} + +// ============ Global StpUtil functions | 全局StpUtil函数 ============ + +// SetManager sets the global Manager (must be called first) | 设置全局Manager(必须先调用此方法) +func SetManager(mgr *Manager) { + stputil.SetManager(mgr) +} + +// GetManager gets the global Manager | 获取全局Manager +func GetManager() *Manager { + return stputil.GetManager() +} + +// ============ Authentication | 登录认证 ============ + +// Login performs user login | 用户登录 +func Login(loginID interface{}, device ...string) (string, error) { + return stputil.Login(loginID, device...) +} + +// LoginByToken performs login with specified token | 使用指定Token登录 +func LoginByToken(loginID interface{}, tokenValue string, device ...string) error { + return stputil.LoginByToken(loginID, tokenValue, device...) +} + +// Logout performs user logout | 用户登出 +func Logout(loginID interface{}, device ...string) error { + return stputil.Logout(loginID, device...) +} + +// LogoutByToken performs logout by token | 根据Token登出 +func LogoutByToken(tokenValue string) error { + return stputil.LogoutByToken(tokenValue) +} + +// IsLogin checks if the user is logged in | 检查用户是否已登录 +func IsLogin(tokenValue string) bool { + return stputil.IsLogin(tokenValue) +} + +// CheckLoginByToken checks login status (throws error if not logged in) | 检查登录状态(未登录抛出错误) +func CheckLoginByToken(tokenValue string) error { + return stputil.CheckLogin(tokenValue) +} + +// GetLoginID gets the login ID from token | 从Token获取登录ID +func GetLoginID(tokenValue string) (string, error) { + return stputil.GetLoginID(tokenValue) +} + +// GetLoginIDNotCheck gets login ID without checking | 获取登录ID(不检查) +func GetLoginIDNotCheck(tokenValue string) (string, error) { + return stputil.GetLoginIDNotCheck(tokenValue) +} + +// GetTokenValue gets the token value for a login ID | 获取登录ID对应的Token值 +func GetTokenValue(loginID interface{}, device ...string) (string, error) { + return stputil.GetTokenValue(loginID, device...) +} + +// GetTokenInfo gets token information | 获取Token信息 +func GetTokenInfo(tokenValue string) (*TokenInfo, error) { + return stputil.GetTokenInfo(tokenValue) +} + +// ============ Kickout | 踢人下线 ============ + +// Kickout kicks out a user session | 踢人下线 +func Kickout(loginID interface{}, device ...string) error { + return stputil.Kickout(loginID, device...) +} + +// ============ Account Disable | 账号封禁 ============ + +// Disable disables an account for specified duration | 封禁账号(指定时长) +func Disable(loginID interface{}, duration time.Duration) error { + return stputil.Disable(loginID, duration) +} + +// IsDisable checks if an account is disabled | 检查账号是否被封禁 +func IsDisable(loginID interface{}) bool { + return stputil.IsDisable(loginID) +} + +// CheckDisableByToken checks if account is disabled (throws error if disabled) | 检查Token对应账号是否被封禁(被封禁则抛出错误) +func CheckDisableByToken(tokenValue string) error { + return stputil.CheckDisable(tokenValue) +} + +// GetDisableTime gets remaining disabled time | 获取账号剩余封禁时间 +func GetDisableTime(loginID interface{}) (int64, error) { + return stputil.GetDisableTime(loginID) +} + +// Untie unties/unlocks an account | 解除账号封禁 +func Untie(loginID interface{}) error { + return stputil.Untie(loginID) +} + +// ============ Permission Check | 权限验证 ============ + +// CheckPermissionByToken checks if the token has specified permission | 检查Token是否拥有指定权限 +func CheckPermissionByToken(tokenValue string, permission string) error { + return stputil.CheckPermission(tokenValue, permission) +} + +// HasPermission checks if the account has specified permission (returns bool) | 检查账号是否拥有指定权限(返回布尔值) +func HasPermission(loginID interface{}, permission string) bool { + return stputil.HasPermission(loginID, permission) +} + +// CheckPermissionAndByToken checks if the token has all specified permissions (AND logic) | 检查Token是否拥有所有指定权限(AND逻辑) +func CheckPermissionAndByToken(tokenValue string, permissions []string) error { + return stputil.CheckPermissionAnd(tokenValue, permissions) +} + +// CheckPermissionOrByToken checks if the token has any of the specified permissions (OR logic) | 检查Token是否拥有指定权限中的任意一个(OR逻辑) +func CheckPermissionOrByToken(tokenValue string, permissions []string) error { + return stputil.CheckPermissionOr(tokenValue, permissions) +} + +// GetPermissionListByToken gets the permission list for a token | 获取Token的权限列表 +func GetPermissionListByToken(tokenValue string) ([]string, error) { + return stputil.GetPermissionList(tokenValue) +} + +// ============ Role Check | 角色验证 ============ + +// CheckRoleByToken checks if the token has specified role | 检查Token是否拥有指定角色 +func CheckRoleByToken(tokenValue string, role string) error { + return stputil.CheckRole(tokenValue, role) +} + +// HasRole checks if the account has specified role (returns bool) | 检查账号是否拥有指定角色(返回布尔值) +func HasRole(loginID interface{}, role string) bool { + return stputil.HasRole(loginID, role) +} + +// CheckRoleAndByToken checks if the token has all specified roles (AND logic) | 检查Token是否拥有所有指定角色(AND逻辑) +func CheckRoleAndByToken(tokenValue string, roles []string) error { + return stputil.CheckRoleAnd(tokenValue, roles) +} + +// CheckRoleOrByToken checks if the token has any of the specified roles (OR logic) | 检查Token是否拥有指定角色中的任意一个(OR逻辑) +func CheckRoleOrByToken(tokenValue string, roles []string) error { + return stputil.CheckRoleOr(tokenValue, roles) +} + +// GetRoleListByToken gets the role list for a token | 获取Token的角色列表 +func GetRoleListByToken(tokenValue string) ([]string, error) { + return stputil.GetRoleList(tokenValue) +} + +// ============ Session Management | Session管理 ============ + +// GetSession gets the session for a login ID | 获取登录ID的Session +func GetSession(loginID interface{}) (*Session, error) { + return stputil.GetSession(loginID) +} + +// GetSessionByToken gets the session by token | 根据Token获取Session +func GetSessionByToken(tokenValue string) (*Session, error) { + return stputil.GetSessionByToken(tokenValue) +} + +// GetTokenSession gets the token session | 获取Token的Session +func GetTokenSession(tokenValue string) (*Session, error) { + return stputil.GetTokenSession(tokenValue) +} + +// ============ Token Renewal | Token续期 ============ +// Note: Token auto-renewal is handled automatically by the manager +// 注意:Token自动续期由管理器自动处理 + +// ============ Security Features | 安全特性 ============ + +// GenerateNonce generates a new nonce token | 生成新的Nonce令牌 +func GenerateNonce() (string, error) { + return stputil.GenerateNonce() +} + +// VerifyNonce verifies a nonce token | 验证Nonce令牌 +func VerifyNonce(nonce string) bool { + return stputil.VerifyNonce(nonce) +} + +// LoginWithRefreshToken performs login and returns refresh token info | 登录并返回刷新令牌信息 +func LoginWithRefreshToken(loginID interface{}, device ...string) (*RefreshTokenInfo, error) { + return stputil.LoginWithRefreshToken(loginID, device...) +} + +// RefreshAccessToken refreshes the access token using a refresh token | 使用刷新令牌刷新访问令牌 +func RefreshAccessToken(refreshToken string) (*RefreshTokenInfo, error) { + return stputil.RefreshAccessToken(refreshToken) +} + +// RevokeRefreshToken revokes a refresh token | 撤销刷新令牌 +func RevokeRefreshToken(refreshToken string) error { + return stputil.RevokeRefreshToken(refreshToken) +} + +// GetOAuth2Server gets the OAuth2 server instance | 获取OAuth2服务器实例 +func GetOAuth2Server() *OAuth2Server { + return stputil.GetOAuth2Server() +} + +// Version Sa-Token-Go version | Sa-Token-Go版本 +const Version = core.Version diff --git a/integrations/hertz/go.mod b/integrations/hertz/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..97a4f8a48d0367e61d6666ee3775b5dee08a62a9 --- /dev/null +++ b/integrations/hertz/go.mod @@ -0,0 +1,12 @@ +module github.com/click33/sa-token-go/integrations/hertz + +go 1.25 + +toolchain go1.24.1 + +require ( + github.com/click33/sa-token-go/core v0.1.8 // indirect + github.com/click33/sa-token-go/stputil v0.1.8 // indirect + github.com/cloudwego/hertz v0.10.3 // indirect + github.com/stretchr/testify v1.11.1 // indirect +) diff --git a/integrations/hertz/go.sum b/integrations/hertz/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..34b4104c47e809e3addb377c6f5b2f0fb5418ce1 --- /dev/null +++ b/integrations/hertz/go.sum @@ -0,0 +1,4 @@ +github.com/click33/sa-token-go/core v0.1.7/go.mod h1:mb3AQAJIXqx9WdULyn5qjufK1j/u+kgB0q+tafHVhgk= +github.com/click33/sa-token-go/stputil v0.1.7/go.mod h1:YY4NzfwVMwPUQLDBk9C5eVLQ08oI3vNSFQhBuZBPtgY= +github.com/cloudwego/hertz v0.10.3/go.mod h1:W5dUFXZPZkyfjMMo3EQrMQbofuvTsctM9IxmhbkuT18= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= diff --git a/integrations/hertz/plugin.go b/integrations/hertz/plugin.go new file mode 100644 index 0000000000000000000000000000000000000000..6a9a55e7d79b7ea957e3123154d9d18590d459a4 --- /dev/null +++ b/integrations/hertz/plugin.go @@ -0,0 +1,287 @@ +package hertz + +import ( + "context" + "errors" + "net/http" + + "github.com/click33/sa-token-go/core" + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/common/utils" + "github.com/cloudwego/hertz/pkg/protocol" +) + +// Plugin Hertz plugin for Sa-Token | Hertz插件 +type Plugin struct { + manager *core.Manager +} + +// NewPlugin creates a Hertz plugin | 创建Hertz插件 +func NewPlugin(manager *core.Manager) *Plugin { + return &Plugin{ + manager: manager, + } +} + +// AuthMiddleware authentication middleware | 认证中间件 +func (p *Plugin) AuthMiddleware() app.HandlerFunc { + return func(ctx context.Context, c *app.RequestContext) { + hCtx := NewHertzContext(c) + saCtx := core.NewContext(hCtx, p.manager) + + // Check login | 检查登录 + if err := saCtx.CheckLogin(); err != nil { + writeErrorResponse(c, err) + c.Abort() + return + } + + // Store Sa-Token context in Hertz context | 将Sa-Token上下文存储到Hertz上下文 + c.Set("satoken", saCtx) + c.Next(ctx) + } +} + +// PathAuthMiddleware path-based authentication middleware | 基于路径的鉴权中间件 +func (p *Plugin) PathAuthMiddleware(config *core.PathAuthConfig) app.HandlerFunc { + return func(ctx context.Context, c *app.RequestContext) { + path := string(c.Path()) + token := string(c.GetHeader(p.manager.GetConfig().TokenName)) + if token == "" { + token = string(c.Cookie(p.manager.GetConfig().TokenName)) + } + + result := core.ProcessAuth(path, token, config, p.manager) + + if result.ShouldReject() { + writeErrorResponse(c, core.NewPathAuthRequiredError(path)) + c.Abort() + return + } + + if result.IsValid && result.TokenInfo != nil { + hCtx := NewHertzContext(c) + saCtx := core.NewContext(hCtx, p.manager) + c.Set("satoken", saCtx) + c.Set("loginID", result.LoginID()) + } + + c.Next(ctx) + } +} + +// PermissionRequired permission validation middleware | 权限验证中间件 +func (p *Plugin) PermissionRequired(permission string) app.HandlerFunc { + return func(ctx context.Context, c *app.RequestContext) { + hCtx := NewHertzContext(c) + saCtx := core.NewContext(hCtx, p.manager) + + // Check login | 检查登录 + if err := saCtx.CheckLogin(); err != nil { + writeErrorResponse(c, err) + c.Abort() + return + } + + // Check permission | 检查权限 + if !saCtx.HasPermission(permission) { + writeErrorResponse(c, core.NewPermissionDeniedError(permission)) + c.Abort() + return + } + + c.Set("satoken", saCtx) + c.Next(ctx) + } +} + +// RoleRequired role validation middleware | 角色验证中间件 +func (p *Plugin) RoleRequired(role string) app.HandlerFunc { + return func(ctx context.Context, c *app.RequestContext) { + hCtx := NewHertzContext(c) + saCtx := core.NewContext(hCtx, p.manager) + + // Check login | 检查登录 + if err := saCtx.CheckLogin(); err != nil { + writeErrorResponse(c, err) + c.Abort() + return + } + + // Check role | 检查角色 + if !saCtx.HasRole(role) { + writeErrorResponse(c, core.NewRoleDeniedError(role)) + c.Abort() + return + } + + c.Set("satoken", saCtx) + c.Next(ctx) + } +} + +// LoginHandler login handler example | 登录处理器示例 +func (p *Plugin) LoginHandler(c *app.RequestContext) { + var req struct { + Username string `json:"username" binding:"required"` + Password string `json:"password" binding:"required"` + Device string `json:"device"` + } + + if err := c.BindJSON(&req); err != nil { + writeErrorResponse(c, core.NewError(core.CodeBadRequest, "invalid request parameters", err)) + return + } + + // Login | 登录 + device := req.Device + if device == "" { + device = "default" + } + + token, err := p.manager.Login(req.Username, device) + if err != nil { + writeErrorResponse(c, core.NewError(core.CodeServerError, "login failed", err)) + return + } + + // Set cookie (optional) | 设置Cookie(可选) + cfg := p.manager.GetConfig() + if cfg.IsReadCookie { + maxAge := int(cfg.Timeout) + if maxAge < 0 { + maxAge = 0 + } + var sameSite protocol.CookieSameSite + switch cfg.CookieConfig.SameSite { + case "Strict": + sameSite = protocol.CookieSameSiteStrictMode + case "Lax": + sameSite = protocol.CookieSameSiteLaxMode + case "None": + sameSite = protocol.CookieSameSiteNoneMode + } + c.SetCookie( + cfg.TokenName, + token, + maxAge, + cfg.CookieConfig.Path, + cfg.CookieConfig.Domain, + sameSite, + cfg.CookieConfig.Secure, + cfg.CookieConfig.HttpOnly, + ) + } + + writeSuccessResponse(c, utils.H{ + "token": token, + }) +} + +// LogoutHandler logout handler | 登出处理器 +func (p *Plugin) LogoutHandler(c *app.RequestContext) { + hCtx := NewHertzContext(c) + saCtx := core.NewContext(hCtx, p.manager) + + loginID, err := saCtx.GetLoginID() + if err != nil { + writeErrorResponse(c, err) + return + } + + if err := p.manager.Logout(loginID); err != nil { + writeErrorResponse(c, core.NewError(core.CodeServerError, "logout failed", err)) + return + } + + writeSuccessResponse(c, utils.H{ + "message": "logout successful", + }) +} + +// UserInfoHandler user info handler example | 获取用户信息处理器示例 +func (p *Plugin) UserInfoHandler(c *app.RequestContext) { + hCtx := NewHertzContext(c) + saCtx := core.NewContext(hCtx, p.manager) + + loginID, err := saCtx.GetLoginID() + if err != nil { + writeErrorResponse(c, err) + return + } + + // Get user permissions and roles | 获取用户权限和角色 + permissions, _ := p.manager.GetPermissions(loginID) + roles, _ := p.manager.GetRoles(loginID) + + writeSuccessResponse(c, utils.H{ + "loginId": loginID, + "permissions": permissions, + "roles": roles, + }) +} + +// GetSaToken gets Sa-Token context from Hertz context | 从Hertz上下文获取Sa-Token上下文 +func GetSaToken(c *app.RequestContext) (*core.SaTokenContext, bool) { + satoken, exists := c.Get("satoken") + if !exists { + return nil, false + } + ctx, ok := satoken.(*core.SaTokenContext) + return ctx, ok +} + +// ============ Error Handling Helpers | 错误处理辅助函数 ============ + +// writeErrorResponse writes a standardized error response | 写入标准化的错误响应 +func writeErrorResponse(c *app.RequestContext, err error) { + var saErr *core.SaTokenError + var code int + var message string + var httpStatus int + + // Check if it's a SaTokenError | 检查是否为SaTokenError + if errors.As(err, &saErr) { + code = saErr.Code + message = saErr.Message + httpStatus = getHTTPStatusFromCode(code) + } else { + // Handle standard errors | 处理标准错误 + code = core.CodeServerError + message = err.Error() + httpStatus = http.StatusInternalServerError + } + + c.JSON(httpStatus, utils.H{ + "code": code, + "message": message, + "error": err.Error(), + }) +} + +// writeSuccessResponse writes a standardized success response | 写入标准化的成功响应 +func writeSuccessResponse(c *app.RequestContext, data interface{}) { + c.JSON(http.StatusOK, utils.H{ + "code": core.CodeSuccess, + "message": "success", + "data": data, + }) +} + +// getHTTPStatusFromCode converts Sa-Token error code to HTTP status | 将Sa-Token错误码转换为HTTP状态码 +func getHTTPStatusFromCode(code int) int { + switch code { + case core.CodeNotLogin: + return http.StatusUnauthorized + case core.CodePermissionDenied: + return http.StatusForbidden + case core.CodeBadRequest: + return http.StatusBadRequest + case core.CodeNotFound: + return http.StatusNotFound + case core.CodeServerError: + return http.StatusInternalServerError + default: + return http.StatusInternalServerError + } +} diff --git a/integrations/kratos/checker_test.go b/integrations/kratos/checker_test.go index 7c231b137c324890b8813f7b336533f5e1bf9fca..2ab1d2be915ccd77305112bf4da85548807adb66 100644 --- a/integrations/kratos/checker_test.go +++ b/integrations/kratos/checker_test.go @@ -259,7 +259,7 @@ func TestCustomChecker(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - checker := &CustomChecker{name: "custom", fn: tt.fn} + checker := &CustomChecker{fn: tt.fn} err := checker.Check(ctx, mgr, loginID) if (err != nil) != tt.wantErr { t.Errorf("CustomChecker.Check() error = %v, wantErr %v", err, tt.wantErr) @@ -380,7 +380,7 @@ func TestCheckerConstructors(t *testing.T) { } // Test NewCustomChecker - if c := NewCustomChecker("test", func(ctx context.Context, manager *manager.Manager, loginID string) error { + if c := NewCustomChecker(func(ctx context.Context, manager *manager.Manager, loginID string) error { return nil }); c == nil { t.Error("NewCustomChecker() should return non-nil") diff --git a/integrations/kratos/go.mod b/integrations/kratos/go.mod index ee423cc2094d9c628546032f1a3159c1f13ecdf0..1762a7ea7a6a7709f240cb4a27da79b3625e90dc 100644 --- a/integrations/kratos/go.mod +++ b/integrations/kratos/go.mod @@ -5,9 +5,9 @@ go 1.24.0 toolchain go1.24.1 require ( - github.com/click33/sa-token-go/core v0.1.5 - github.com/click33/sa-token-go/storage/memory v0.1.5 - github.com/click33/sa-token-go/stputil v0.1.5 + github.com/click33/sa-token-go/core v0.1.8 + github.com/click33/sa-token-go/storage/memory v0.1.8 + github.com/click33/sa-token-go/stputil v0.1.8 github.com/go-kratos/kratos/v2 v2.9.1 ) diff --git a/integrations/kratos/options.go b/integrations/kratos/options.go index 971c0d893e866250cce11db5705e948690c2651c..b8e3f62538c883db3fd04221794f7409f52e8e67 100644 --- a/integrations/kratos/options.go +++ b/integrations/kratos/options.go @@ -2,9 +2,9 @@ package kratos import ( "context" - "github.com/click33/sa-token-go/core" "net/http" + "github.com/click33/sa-token-go/core" "github.com/go-kratos/kratos/v2/errors" ) diff --git a/integrations/kratos/plugin.go b/integrations/kratos/plugin.go index 76db2c4704e854b77b2741198408af0243b5caab..b28332f6bba3a18f49322fcceeb39e2b42f0c2ae 100644 --- a/integrations/kratos/plugin.go +++ b/integrations/kratos/plugin.go @@ -2,6 +2,7 @@ package kratos import ( "context" + "net/http" "sort" "strings" @@ -49,7 +50,6 @@ func (e *Plugin) Server() middleware.Middleware { return func(ctx context.Context, req interface{}) (reply interface{}, err error) { info, ok := transport.FromServerContext(ctx) if !ok { - // 无法获取传输层信息,直接放行 return handler(ctx, req) } @@ -90,6 +90,48 @@ func (e *Plugin) Server() middleware.Middleware { } } +// PathAuthMiddleware 基于路径的鉴权中间件 +// 使用 Ant 风格通配符进行路径匹配 +func (e *Plugin) PathAuthMiddleware(config *core.PathAuthConfig) middleware.Middleware { + return func(handler middleware.Handler) middleware.Handler { + return func(ctx context.Context, req interface{}) (reply interface{}, err error) { + info, ok := transport.FromServerContext(ctx) + if !ok { + return handler(ctx, req) + } + + // 获取实际的 HTTP 路径 + var path string + if htr, ok := info.(interface{ Request() *http.Request }); ok { + path = htr.Request().URL.Path + } else { + // 如果无法获取路径,使用 operation 作为后备 + path = info.Operation() + } + + // 获取 token + kratosContext := NewKratosContext(ctx) + saCtx := core.NewContext(kratosContext, e.manager) + token := saCtx.GetTokenValue() + + // 处理路径鉴权 + result := core.ProcessAuth(path, token, config, e.manager) + + if result.ShouldReject() { + return nil, e.options.ErrorHandler(ctx, core.NewPathAuthRequiredError(path)) + } + + // 如果 token 有效,将相关信息存储到 context + if result.IsValid && result.TokenInfo != nil { + ctx = context.WithValue(ctx, "satoken", saCtx) + ctx = context.WithValue(ctx, "loginID", result.LoginID()) + } + + return handler(ctx, req) + } + } +} + // ========== 规则构建器 ========== // RuleBuilder 规则构建器(链式API) diff --git a/storage/memory/go.mod b/storage/memory/go.mod index ef4bdfc69c03f032ad098454a84b7b333f71fe27..0738598fbb87497a4e60b046368e90410f59e3e8 100644 --- a/storage/memory/go.mod +++ b/storage/memory/go.mod @@ -2,6 +2,6 @@ module github.com/click33/sa-token-go/storage/memory go 1.23.0 -require github.com/click33/sa-token-go/core v0.1.5 +require github.com/click33/sa-token-go/core v0.1.8 replace github.com/click33/sa-token-go/core => ../../core diff --git a/storage/redis/go.mod b/storage/redis/go.mod index 4901b8104d1459d9b58fb8977ac51ea765b0aefc..84da4f69c5dd1c66d80e36e4607d6c94e53c1182 100644 --- a/storage/redis/go.mod +++ b/storage/redis/go.mod @@ -3,7 +3,7 @@ module github.com/click33/sa-token-go/storage/redis go 1.23.0 require ( - github.com/click33/sa-token-go/core v0.1.5 + github.com/click33/sa-token-go/core v0.1.8 github.com/redis/go-redis/v9 v9.5.1 ) diff --git a/stputil/go.mod b/stputil/go.mod index 9f31aa952445ce3e4d222677e27d1c1568c15a09..af14660af3a6f40dc0cb9ab360844a7a0f072896 100644 --- a/stputil/go.mod +++ b/stputil/go.mod @@ -2,13 +2,13 @@ module github.com/click33/sa-token-go/stputil go 1.23.0 -require github.com/click33/sa-token-go/core v0.1.5 +require github.com/click33/sa-token-go/core v0.1.8 require ( - github.com/golang-jwt/jwt/v5 v5.2.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/panjf2000/ants/v2 v2.11.3 // indirect - golang.org/x/sync v0.16.0 // indirect + golang.org/x/sync v0.19.0 // indirect ) replace github.com/click33/sa-token-go/core => ../core diff --git a/stputil/go.sum b/stputil/go.sum index dda2c2d233051a01e2f37412aff3d6dc1bf6f103..3b657b9d4f82342b5e59095fc97b1ea9c655f209 100644 --- a/stputil/go.sum +++ b/stputil/go.sum @@ -1,8 +1,20 @@ +github.com/click33/sa-token-go/storage/memory v0.1.6 h1:iGFEy+HtTJLOpKnbIMbgpXyKotsKpPQu6wWTZVOXQis= +github.com/click33/sa-token-go/storage/memory v0.1.6/go.mod h1:YNojcgyLC/uFrmReZLePCDQ5WK2fo2WWGRjRMvXVH74= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg= +github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/stputil/stplogic.go b/stputil/stplogic.go new file mode 100644 index 0000000000000000000000000000000000000000..f75dcbd0acc79d1fe83d8ba8069bf37627c4c0c1 --- /dev/null +++ b/stputil/stplogic.go @@ -0,0 +1,379 @@ +package stputil + +import ( + "fmt" + "github.com/click33/sa-token-go/core/manager" + "github.com/click33/sa-token-go/core/oauth2" + "github.com/click33/sa-token-go/core/security" + "github.com/click33/sa-token-go/core/session" + "sync" + "time" +) + +var ( + TokenValueKey = "stplogic:tokenvalue" + LoginIdKey = "stplogic:loginid" + PermissionsKey = "stplogic:permissions" + RolesKey = "stplogic:roles" +) + +type StpLogic struct { + manager *manager.Manager + mu sync.RWMutex +} + +func NewStpLogic(mrg *manager.Manager) *StpLogic { + return &StpLogic{manager: mrg} +} + +// GetManager gets the global Manager | 获取全局Manager +func (s *StpLogic) GetManager() *manager.Manager { + s.mu.RLock() + defer s.mu.RUnlock() + if s.manager == nil { + panic("StpLogic not initialized.") + } + return s.manager +} + +func (s *StpLogic) SetManager(manager *manager.Manager) { + s.mu.Lock() + defer s.mu.Unlock() + s.manager = manager +} + +// ============ Authentication | 登录认证 ============ + +// Login performs user login | 用户登录 +func (s *StpLogic) Login(loginID interface{}, device ...string) (string, error) { + return s.manager.Login(toString(loginID), device...) +} + +// LoginByToken performs login with specified token | 使用指定Token登录 +func (s *StpLogic) LoginByToken(loginID interface{}, tokenValue string, device ...string) error { + return s.manager.LoginByToken(toString(loginID), tokenValue, device...) +} + +// Logout performs user logout | 用户登出 +func (s *StpLogic) Logout(loginID interface{}, device ...string) error { + return s.manager.Logout(toString(loginID), device...) +} + +// LogoutByToken performs logout by token | 根据Token登出 +func (s *StpLogic) LogoutByToken(tokenValue string) error { + return s.manager.LogoutByToken(tokenValue) +} + +// IsLogin checks if the user is logged in | 检查用户是否已登录 +func (s *StpLogic) IsLogin(tokenValue string) bool { + return s.manager.IsLogin(tokenValue) +} + +// CheckLogin checks login status (throws error if not logged in) | 检查登录状态(未登录抛出错误) +func (s *StpLogic) CheckLogin(tokenValue string) error { + return s.manager.CheckLogin(tokenValue) +} + +// GetLoginID gets the login ID from token | 从Token获取登录ID +func (s *StpLogic) GetLoginID(tokenValue string) (string, error) { + return s.manager.GetLoginID(tokenValue) +} + +// GetLoginIDNotCheck gets login ID without checking | 获取登录ID(不检查) +func (s *StpLogic) GetLoginIDNotCheck(tokenValue string) (string, error) { + return s.manager.GetLoginIDNotCheck(tokenValue) +} + +// GetTokenValue gets the token value for a login ID | 获取登录ID对应的Token值 +func (s *StpLogic) GetTokenValue(loginID interface{}, device ...string) (string, error) { + return s.manager.GetTokenValue(toString(loginID), device...) +} + +// GetTokenInfo gets token information | 获取Token信息 +func (s *StpLogic) GetTokenInfo(tokenValue string) (*manager.TokenInfo, error) { + return s.manager.GetTokenInfo(tokenValue) +} + +// ============ Kickout | 踢人下线 ============ + +// Kickout kicks out a user session | 踢人下线 +func (s *StpLogic) Kickout(loginID interface{}, device ...string) error { + return s.manager.Kickout(toString(loginID), device...) +} + +// ============ Account Disable | 账号封禁 ============ + +// Disable disables an account for specified duration | 封禁账号(指定时长) +func (s *StpLogic) Disable(loginID interface{}, duration time.Duration) error { + return s.manager.Disable(toString(loginID), duration) +} + +// Untie re-enables a disabled account | 解封账号 +func (s *StpLogic) Untie(loginID interface{}) error { + return s.manager.Untie(toString(loginID)) +} + +// IsDisable checks if an account is disabled | 检查账号是否被封禁 +func (s *StpLogic) IsDisable(loginID interface{}) bool { + return s.manager.IsDisable(toString(loginID)) +} + +// GetDisableTime gets remaining disable time in seconds | 获取剩余封禁时间(秒) +func (s *StpLogic) GetDisableTime(loginID interface{}) (int64, error) { + return s.manager.GetDisableTime(toString(loginID)) +} + +// ============ Session Management | Session管理 ============ + +// GetSession gets session by login ID | 根据登录ID获取Session +func (s *StpLogic) GetSession(loginID interface{}) (*session.Session, error) { + return s.manager.GetSession(toString(loginID)) +} + +// GetSessionByToken gets session by token | 根据Token获取Session +func (s *StpLogic) GetSessionByToken(tokenValue string) (*session.Session, error) { + return s.manager.GetSessionByToken(tokenValue) +} + +// DeleteSession deletes a session | 删除Session +func (s *StpLogic) DeleteSession(loginID interface{}) error { + return s.manager.DeleteSession(toString(loginID)) +} + +// ============ Permission Verification | 权限验证 ============ + +// SetPermissions sets permissions for a login ID | 设置用户权限 +func (s *StpLogic) SetPermissions(loginID interface{}, permissions []string) error { + return s.manager.SetPermissions(toString(loginID), permissions) +} + +// GetPermissions gets permission list | 获取权限列表 +func (s *StpLogic) GetPermissions(loginID interface{}) ([]string, error) { + return s.manager.GetPermissions(toString(loginID)) +} + +// HasPermission checks if has specified permission | 检查是否拥有指定权限 +func (s *StpLogic) HasPermission(loginID interface{}, permission string) bool { + return s.manager.HasPermission(toString(loginID), permission) +} + +// HasPermissionsAnd checks if has all permissions (AND logic) | 检查是否拥有所有权限(AND逻辑) +func (s *StpLogic) HasPermissionsAnd(loginID interface{}, permissions []string) bool { + return s.manager.HasPermissionsAnd(toString(loginID), permissions) +} + +// HasPermissionsOr checks if has any permission (OR logic) | 检查是否拥有任一权限(OR逻辑) +func (s *StpLogic) HasPermissionsOr(loginID interface{}, permissions []string) bool { + return s.manager.HasPermissionsOr(toString(loginID), permissions) +} + +// ============ Role Management | 角色管理 ============ + +// SetRoles sets roles for a login ID | 设置用户角色 +func (s *StpLogic) SetRoles(loginID interface{}, roles []string) error { + return s.manager.SetRoles(toString(loginID), roles) +} + +// GetRoles gets role list | 获取角色列表 +func (s *StpLogic) GetRoles(loginID interface{}) ([]string, error) { + return s.manager.GetRoles(toString(loginID)) +} + +// HasRole checks if has specified role | 检查是否拥有指定角色 +func (s *StpLogic) HasRole(loginID interface{}, role string) bool { + return s.manager.HasRole(toString(loginID), role) +} + +// HasRolesAnd checks if has all roles (AND logic) | 检查是否拥有所有角色(AND逻辑) +func (s *StpLogic) HasRolesAnd(loginID interface{}, roles []string) bool { + return s.manager.HasRolesAnd(toString(loginID), roles) +} + +// HasRolesOr 检查是否拥有任一角色(OR) +func (s *StpLogic) HasRolesOr(loginID interface{}, roles []string) bool { + return s.manager.HasRolesOr(toString(loginID), roles) +} + +// ============ Token标签 ============ + +// SetTokenTag 设置Token标签 +func (s *StpLogic) SetTokenTag(tokenValue, tag string) error { + return s.manager.SetTokenTag(tokenValue, tag) +} + +// GetTokenTag 获取Token标签 +func (s *StpLogic) GetTokenTag(tokenValue string) (string, error) { + return s.manager.GetTokenTag(tokenValue) +} + +// ============ 会话查询 ============ + +// GetTokenValueList 获取指定账号的所有Token +func (s *StpLogic) GetTokenValueList(loginID interface{}) ([]string, error) { + return s.manager.GetTokenValueListByLoginID(toString(loginID)) +} + +// GetSessionCount 获取指定账号的Session数量 +func (s *StpLogic) GetSessionCount(loginID interface{}) (int, error) { + return s.manager.GetSessionCountByLoginID(toString(loginID)) +} + +func (s *StpLogic) GenerateNonce() (string, error) { + if s.manager == nil { + panic("Manager not initialized.") + } + return s.manager.GenerateNonce() +} + +func (s *StpLogic) VerifyNonce(nonce string) bool { + if s.manager == nil { + panic("Manager not initialized.") + } + return s.manager.VerifyNonce(nonce) +} + +func (s *StpLogic) LoginWithRefreshToken(loginID interface{}, device ...string) (*security.RefreshTokenInfo, error) { + if s.manager == nil { + panic("Manager not initialized.") + } + deviceType := "default" + if len(device) > 0 { + deviceType = device[0] + } + return s.manager.LoginWithRefreshToken(fmt.Sprintf("%v", loginID), deviceType) +} + +func (s *StpLogic) RefreshAccessToken(refreshToken string) (*security.RefreshTokenInfo, error) { + if s.manager == nil { + panic("Manager not initialized.") + } + return s.manager.RefreshAccessToken(refreshToken) +} + +func (s *StpLogic) RevokeRefreshToken(refreshToken string) error { + if s.manager == nil { + panic("Manager not initialized.") + } + return s.manager.RevokeRefreshToken(refreshToken) +} + +func (s *StpLogic) GetOAuth2Server() *oauth2.OAuth2Server { + if s.manager == nil { + panic("Manager not initialized.") + } + return s.manager.GetOAuth2Server() +} + +// ============ Check Functions for Token-based operations | 基于Token的检查函数 ============ + +// CheckDisable checks if the account associated with the token is disabled | 检查Token对应账号是否被封禁 +func (s *StpLogic) CheckDisable(tokenValue string) error { + loginID, err := s.GetLoginID(tokenValue) + if err != nil { + return err + } + if s.IsDisable(loginID) { + return fmt.Errorf("account is disabled") + } + return nil +} + +// CheckPermission checks if the token has the specified permission | 检查Token是否拥有指定权限 +func (s *StpLogic) CheckPermission(tokenValue string, permission string) error { + loginID, err := s.GetLoginID(tokenValue) + if err != nil { + return err + } + if !s.HasPermission(loginID, permission) { + return fmt.Errorf("permission denied: %s", permission) + } + return nil +} + +// CheckPermissionAnd checks if the token has all specified permissions | 检查Token是否拥有所有指定权限 +func (s *StpLogic) CheckPermissionAnd(tokenValue string, permissions []string) error { + loginID, err := s.GetLoginID(tokenValue) + if err != nil { + return err + } + if !s.HasPermissionsAnd(loginID, permissions) { + return fmt.Errorf("permission denied: %v", permissions) + } + return nil +} + +// CheckPermissionOr checks if the token has any of the specified permissions | 检查Token是否拥有任一指定权限 +func (s *StpLogic) CheckPermissionOr(tokenValue string, permissions []string) error { + loginID, err := s.GetLoginID(tokenValue) + if err != nil { + return err + } + if !s.HasPermissionsOr(loginID, permissions) { + return fmt.Errorf("permission denied: %v", permissions) + } + return nil +} + +// GetPermissionList gets permission list for the token | 获取Token对应的权限列表 +func (s *StpLogic) GetPermissionList(tokenValue string) ([]string, error) { + loginID, err := s.GetLoginID(tokenValue) + if err != nil { + return nil, err + } + return s.GetPermissions(loginID) +} + +// CheckRole checks if the token has the specified role | 检查Token是否拥有指定角色 +func (s *StpLogic) CheckRole(tokenValue string, role string) error { + loginID, err := s.GetLoginID(tokenValue) + if err != nil { + return err + } + if !s.HasRole(loginID, role) { + return fmt.Errorf("role denied: %s", role) + } + return nil +} + +// CheckRoleAnd checks if the token has all specified roles | 检查Token是否拥有所有指定角色 +func (s *StpLogic) CheckRoleAnd(tokenValue string, roles []string) error { + loginID, err := s.GetLoginID(tokenValue) + if err != nil { + return err + } + if !s.HasRolesAnd(loginID, roles) { + return fmt.Errorf("role denied: %v", roles) + } + return nil +} + +// CheckRoleOr checks if the token has any of the specified roles | 检查Token是否拥有任一指定角色 +func (s *StpLogic) CheckRoleOr(tokenValue string, roles []string) error { + loginID, err := s.GetLoginID(tokenValue) + if err != nil { + return err + } + if !s.HasRolesOr(loginID, roles) { + return fmt.Errorf("role denied: %v", roles) + } + return nil +} + +// GetRoleList gets role list for the token | 获取Token对应的角色列表 +func (s *StpLogic) GetRoleList(tokenValue string) ([]string, error) { + loginID, err := GetLoginID(tokenValue) + if err != nil { + return nil, err + } + return GetRoles(loginID) +} + +// GetTokenSession gets session for the token | 获取Token对应的Session +func (s *StpLogic) GetTokenSession(tokenValue string) (*session.Session, error) { + return GetSessionByToken(tokenValue) +} + +// CloseManager Closes the manager and releases all resources | 关闭管理器并释放所有资源 +func (s *StpLogic) CloseManager() { + s.manager.CloseManager() +} diff --git a/stputil/stplogic_test.go b/stputil/stplogic_test.go new file mode 100644 index 0000000000000000000000000000000000000000..547aa3bc974af6fc69629163f688bfe5f42713e1 --- /dev/null +++ b/stputil/stplogic_test.go @@ -0,0 +1,254 @@ +package stputil + +import ( + "testing" + "time" + + "github.com/click33/sa-token-go/core/config" + "github.com/click33/sa-token-go/core/manager" + "github.com/click33/sa-token-go/core/security" + "github.com/click33/sa-token-go/storage/memory" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func testConfig() *config.Config { + return &config.Config{ + TokenName: "satoken", + Timeout: 3600, + IsConcurrent: true, + IsShare: true, + MaxLoginCount: -1, + } +} + +func newTestStpLogic(t *testing.T) *StpLogic { + t.Helper() + + storage := memory.NewStorage() + mgr := manager.NewManager(storage, testConfig()) + logic := NewStpLogic(mgr) + + SetStpLogic(logic) + + t.Cleanup(func() { + logic.CloseManager() + SetStpLogic(nil) + }) + + return logic +} + +func TestStpLogic_ManagerAccessors(t *testing.T) { + logic := newTestStpLogic(t) + + mgr := logic.GetManager() + require.NotNil(t, mgr) + + nextMgr := manager.NewManager(memory.NewStorage(), testConfig()) + logic.SetManager(nextMgr) + assert.Equal(t, nextMgr, logic.GetManager()) +} + +func TestStpLogic_AuthAndSessionFlow(t *testing.T) { + logic := newTestStpLogic(t) + + token, err := logic.Login("user-auth", "web") + require.NoError(t, err) + require.NotEmpty(t, token) + + assert.NoError(t, logic.LoginByToken("user-auth", token, "web")) + assert.True(t, logic.IsLogin(token)) + assert.NoError(t, logic.CheckLogin(token)) + + loginID, err := logic.GetLoginID(token) + require.NoError(t, err) + assert.Equal(t, "user-auth", loginID) + + loginIDUnchecked, err := logic.GetLoginIDNotCheck(token) + require.NoError(t, err) + assert.Equal(t, "user-auth", loginIDUnchecked) + + tokenValue, err := logic.GetTokenValue("user-auth", "web") + require.NoError(t, err) + assert.Equal(t, token, tokenValue) + + tokenInfo, err := logic.GetTokenInfo(token) + require.NoError(t, err) + assert.Equal(t, "user-auth", tokenInfo.LoginID) + + sess, err := logic.GetSession("user-auth") + require.NoError(t, err) + assert.NotNil(t, sess) + + sessByToken, err := logic.GetSessionByToken(token) + require.NoError(t, err) + assert.NotNil(t, sessByToken) + + tokenSess, err := logic.GetTokenSession(token) + require.NoError(t, err) + assert.NotNil(t, tokenSess) + + tokenList, err := logic.GetTokenValueList("user-auth") + require.NoError(t, err) + assert.Contains(t, tokenList, token) + + sessCount, err := logic.GetSessionCount("user-auth") + require.NoError(t, err) + assert.Equal(t, 1, sessCount) + + require.NoError(t, logic.Logout("user-auth", "web")) + assert.False(t, logic.IsLogin(token)) + + token2, err := logic.Login("user-auth", "web") + require.NoError(t, err) + require.NoError(t, logic.LogoutByToken(token2)) + assert.False(t, logic.IsLogin(token2)) + + token3, err := logic.Login("user-auth", "web") + require.NoError(t, err) + require.NoError(t, logic.Kickout("user-auth", "web")) + assert.False(t, logic.IsLogin(token3)) + + token4, err := logic.Login("user-auth", "web") + require.NoError(t, err) + require.NoError(t, logic.DeleteSession("user-auth")) + sessAfterDelete, err := logic.GetSession("user-auth") + require.NoError(t, err) + assert.True(t, sessAfterDelete.IsEmpty()) + assert.True(t, logic.IsLogin(token4)) +} + +func TestStpLogic_Permissions(t *testing.T) { + logic := newTestStpLogic(t) + + token, err := logic.Login("user-perm") + require.NoError(t, err) + + perms := []string{"user.read", "user.write"} + require.NoError(t, logic.SetPermissions("user-perm", perms)) + + gotPerms, err := logic.GetPermissions("user-perm") + require.NoError(t, err) + assert.ElementsMatch(t, perms, gotPerms) + + assert.True(t, logic.HasPermission("user-perm", "user.read")) + assert.True(t, logic.HasPermissionsAnd("user-perm", []string{"user.read", "user.write"})) + assert.True(t, logic.HasPermissionsOr("user-perm", []string{"user.delete", "user.read"})) + + assert.NoError(t, logic.CheckPermission(token, "user.read")) + assert.NoError(t, logic.CheckPermissionAnd(token, []string{"user.read", "user.write"})) + assert.NoError(t, logic.CheckPermissionOr(token, []string{"user.delete", "user.read"})) + + err = logic.CheckPermission(token, "user.delete") + assert.Error(t, err) + + permList, err := logic.GetPermissionList(token) + require.NoError(t, err) + assert.ElementsMatch(t, perms, permList) +} + +func TestStpLogic_Roles(t *testing.T) { + logic := newTestStpLogic(t) + + token, err := logic.Login("user-role") + require.NoError(t, err) + + roles := []string{"Admin", "User"} + require.NoError(t, logic.SetRoles("user-role", roles)) + + gotRoles, err := logic.GetRoles("user-role") + require.NoError(t, err) + assert.ElementsMatch(t, roles, gotRoles) + + assert.True(t, logic.HasRole("user-role", "Admin")) + assert.True(t, logic.HasRolesAnd("user-role", []string{"Admin", "User"})) + assert.True(t, logic.HasRolesOr("user-role", []string{"Guest", "Admin"})) + + assert.NoError(t, logic.CheckRole(token, "Admin")) + assert.NoError(t, logic.CheckRoleAnd(token, []string{"Admin", "User"})) + assert.NoError(t, logic.CheckRoleOr(token, []string{"Guest", "Admin"})) + + err = logic.CheckRole(token, "Guest") + assert.Error(t, err) + + roleList, err := GetRoleList(token) + require.NoError(t, err) + assert.ElementsMatch(t, roles, roleList) +} + +func TestStpLogic_DisableAndUntie(t *testing.T) { + logic := newTestStpLogic(t) + + token, err := logic.Login("user-disable") + require.NoError(t, err) + + assert.NoError(t, logic.CheckDisable(token)) + + require.NoError(t, logic.Disable("user-disable", time.Minute)) + assert.True(t, logic.IsDisable("user-disable")) + + disableTTL, err := logic.GetDisableTime("user-disable") + require.NoError(t, err) + assert.Greater(t, disableTTL, int64(0)) + + assert.Error(t, logic.CheckDisable(token)) + + require.NoError(t, logic.Untie("user-disable")) + assert.False(t, logic.IsDisable("user-disable")) + + newToken, err := logic.Login("user-disable") + require.NoError(t, err) + assert.NoError(t, logic.CheckDisable(newToken)) +} + +func TestStpLogic_TokenTags(t *testing.T) { + logic := newTestStpLogic(t) + + err := logic.SetTokenTag("token-tag", "demo") + assert.Error(t, err) + + _, err = logic.GetTokenTag("token-tag") + assert.Error(t, err) +} + +func TestStpLogic_Nonce(t *testing.T) { + logic := newTestStpLogic(t) + + nonce, err := logic.GenerateNonce() + require.NoError(t, err) + require.NotEmpty(t, nonce) + + assert.True(t, logic.VerifyNonce(nonce)) + assert.False(t, logic.VerifyNonce(nonce)) +} + +func TestStpLogic_RefreshTokenFlow(t *testing.T) { + logic := newTestStpLogic(t) + + tokenInfo, err := logic.LoginWithRefreshToken("user-refresh", "mobile") + require.NoError(t, err) + require.NotEmpty(t, tokenInfo.AccessToken) + require.NotEmpty(t, tokenInfo.RefreshToken) + + assert.True(t, logic.IsLogin(tokenInfo.AccessToken)) + + refreshed, err := logic.RefreshAccessToken(tokenInfo.RefreshToken) + if err == nil { + require.NotEmpty(t, refreshed.AccessToken) + assert.True(t, logic.IsLogin(refreshed.AccessToken)) + } else { + assert.ErrorIs(t, err, security.ErrInvalidRefreshData) + } + + require.NoError(t, logic.RevokeRefreshToken(tokenInfo.RefreshToken)) + _, err = logic.RefreshAccessToken(tokenInfo.RefreshToken) + assert.ErrorIs(t, err, security.ErrInvalidRefreshToken) +} + +func TestStpLogic_OAuth2AndClose(t *testing.T) { + logic := newTestStpLogic(t) + + assert.NotNil(t, logic.GetOAuth2Server()) + assert.NotPanics(t, func() { logic.CloseManager() }) +} diff --git a/stputil/stputil.go b/stputil/stputil.go index 4a8424022741142ec64097cedd6436df645d0445..1edc019dffbd73d081ce1058486bc2e865d1f559 100644 --- a/stputil/stputil.go +++ b/stputil/stputil.go @@ -13,212 +13,229 @@ import ( // Global Manager instance | 全局Manager实例 var ( - globalManager *manager.Manager - once sync.Once - mu sync.RWMutex + globalLogic *StpLogic + //globalManager *manager.Manager + once sync.Once + mu sync.RWMutex ) // SetManager sets the global Manager (must be called first) | 设置全局Manager(必须先调用此方法) func SetManager(mgr *manager.Manager) { - mu.Lock() - defer mu.Unlock() - globalManager = mgr + if globalLogic == nil { + globalLogic = NewStpLogic(mgr) + } + globalLogic.SetManager(mgr) } // GetManager gets the global Manager | 获取全局Manager func GetManager() *manager.Manager { - mu.RLock() - defer mu.RUnlock() - if globalManager == nil { - panic("StpUtil not initialized, please call SetManager() first or use builder.NewBuilder().Build()") + if globalLogic == nil { + panic("StpUtil not initialized, please call SetManager() first") + } + if globalLogic.GetManager() == nil { + panic("StpUtil not initialized, please call SetManager() first") } - return globalManager + return globalLogic.GetManager() } // CloseManager closes global Manager and releases resources | 关闭全局 Manager 并释放资源 func CloseManager() { + if globalLogic != nil { + globalLogic.CloseManager() + globalLogic.SetManager(nil) // 置 nil 避免后续误用 + } +} + +// ============ StpLogic ============ + +// SetStpLogic sets the global StpLogic instance | 设置全局 StpLogic 实例 +func SetStpLogic(logic *StpLogic) { mu.Lock() defer mu.Unlock() - if globalManager != nil { - globalManager.CloseManager() - globalManager = nil // 置 nil 避免后续误用 - } + globalLogic = logic +} + +// GetStpLogic gets the global StpLogic instance | 获取全局 StpLogic 实例 +func GetStpLogic() *StpLogic { + mu.Lock() + defer mu.Unlock() + return globalLogic } // ============ Authentication | 登录认证 ============ // Login performs user login | 用户登录 func Login(loginID interface{}, device ...string) (string, error) { - return GetManager().Login(toString(loginID), device...) + return globalLogic.Login(toString(loginID), device...) } // LoginByToken performs login with specified token | 使用指定Token登录 func LoginByToken(loginID interface{}, tokenValue string, device ...string) error { - return GetManager().LoginByToken(toString(loginID), tokenValue, device...) + return globalLogic.LoginByToken(toString(loginID), tokenValue, device...) } // Logout performs user logout | 用户登出 func Logout(loginID interface{}, device ...string) error { - return GetManager().Logout(toString(loginID), device...) + return globalLogic.Logout(toString(loginID), device...) } // LogoutByToken performs logout by token | 根据Token登出 func LogoutByToken(tokenValue string) error { - return GetManager().LogoutByToken(tokenValue) + return globalLogic.LogoutByToken(tokenValue) } // IsLogin checks if the user is logged in | 检查用户是否已登录 func IsLogin(tokenValue string) bool { - return GetManager().IsLogin(tokenValue) + return globalLogic.IsLogin(tokenValue) } // CheckLogin checks login status (throws error if not logged in) | 检查登录状态(未登录抛出错误) func CheckLogin(tokenValue string) error { - return GetManager().CheckLogin(tokenValue) + return globalLogic.CheckLogin(tokenValue) } // GetLoginID gets the login ID from token | 从Token获取登录ID func GetLoginID(tokenValue string) (string, error) { - return GetManager().GetLoginID(tokenValue) + return globalLogic.GetLoginID(tokenValue) } // GetLoginIDNotCheck gets login ID without checking | 获取登录ID(不检查) func GetLoginIDNotCheck(tokenValue string) (string, error) { - return GetManager().GetLoginIDNotCheck(tokenValue) + return globalLogic.GetLoginIDNotCheck(tokenValue) } // GetTokenValue gets the token value for a login ID | 获取登录ID对应的Token值 func GetTokenValue(loginID interface{}, device ...string) (string, error) { - return GetManager().GetTokenValue(toString(loginID), device...) + return globalLogic.GetTokenValue(toString(loginID), device...) } // GetTokenInfo gets token information | 获取Token信息 func GetTokenInfo(tokenValue string) (*manager.TokenInfo, error) { - return GetManager().GetTokenInfo(tokenValue) + return globalLogic.GetTokenInfo(tokenValue) } // ============ Kickout | 踢人下线 ============ // Kickout kicks out a user session | 踢人下线 func Kickout(loginID interface{}, device ...string) error { - return GetManager().Kickout(toString(loginID), device...) + return globalLogic.Kickout(toString(loginID), device...) } // ============ Account Disable | 账号封禁 ============ // Disable disables an account for specified duration | 封禁账号(指定时长) func Disable(loginID interface{}, duration time.Duration) error { - return GetManager().Disable(toString(loginID), duration) + return globalLogic.Disable(toString(loginID), duration) } // Untie re-enables a disabled account | 解封账号 func Untie(loginID interface{}) error { - return GetManager().Untie(toString(loginID)) + return globalLogic.Untie(toString(loginID)) } // IsDisable checks if an account is disabled | 检查账号是否被封禁 func IsDisable(loginID interface{}) bool { - return GetManager().IsDisable(toString(loginID)) + return globalLogic.IsDisable(toString(loginID)) } // GetDisableTime gets remaining disable time in seconds | 获取剩余封禁时间(秒) func GetDisableTime(loginID interface{}) (int64, error) { - return GetManager().GetDisableTime(toString(loginID)) + return globalLogic.GetDisableTime(toString(loginID)) } // ============ Session Management | Session管理 ============ // GetSession gets session by login ID | 根据登录ID获取Session func GetSession(loginID interface{}) (*session.Session, error) { - return GetManager().GetSession(toString(loginID)) + return globalLogic.GetSession(toString(loginID)) } // GetSessionByToken gets session by token | 根据Token获取Session func GetSessionByToken(tokenValue string) (*session.Session, error) { - return GetManager().GetSessionByToken(tokenValue) + return globalLogic.GetSessionByToken(tokenValue) } // DeleteSession deletes a session | 删除Session func DeleteSession(loginID interface{}) error { - return GetManager().DeleteSession(toString(loginID)) + return globalLogic.DeleteSession(toString(loginID)) } // ============ Permission Verification | 权限验证 ============ // SetPermissions sets permissions for a login ID | 设置用户权限 func SetPermissions(loginID interface{}, permissions []string) error { - return GetManager().SetPermissions(toString(loginID), permissions) + return globalLogic.SetPermissions(toString(loginID), permissions) } // GetPermissions gets permission list | 获取权限列表 func GetPermissions(loginID interface{}) ([]string, error) { - return GetManager().GetPermissions(toString(loginID)) + return globalLogic.GetPermissions(toString(loginID)) } // HasPermission checks if has specified permission | 检查是否拥有指定权限 func HasPermission(loginID interface{}, permission string) bool { - return GetManager().HasPermission(toString(loginID), permission) + return globalLogic.HasPermission(toString(loginID), permission) } // HasPermissionsAnd checks if has all permissions (AND logic) | 检查是否拥有所有权限(AND逻辑) func HasPermissionsAnd(loginID interface{}, permissions []string) bool { - return GetManager().HasPermissionsAnd(toString(loginID), permissions) + return globalLogic.HasPermissionsAnd(toString(loginID), permissions) } // HasPermissionsOr checks if has any permission (OR logic) | 检查是否拥有任一权限(OR逻辑) func HasPermissionsOr(loginID interface{}, permissions []string) bool { - return GetManager().HasPermissionsOr(toString(loginID), permissions) + return globalLogic.HasPermissionsOr(toString(loginID), permissions) } // ============ Role Management | 角色管理 ============ // SetRoles sets roles for a login ID | 设置用户角色 func SetRoles(loginID interface{}, roles []string) error { - return GetManager().SetRoles(toString(loginID), roles) + return globalLogic.SetRoles(toString(loginID), roles) } // GetRoles gets role list | 获取角色列表 func GetRoles(loginID interface{}) ([]string, error) { - return GetManager().GetRoles(toString(loginID)) + return globalLogic.GetRoles(toString(loginID)) } // HasRole checks if has specified role | 检查是否拥有指定角色 func HasRole(loginID interface{}, role string) bool { - return GetManager().HasRole(toString(loginID), role) + return globalLogic.HasRole(toString(loginID), role) } // HasRolesAnd checks if has all roles (AND logic) | 检查是否拥有所有角色(AND逻辑) func HasRolesAnd(loginID interface{}, roles []string) bool { - return GetManager().HasRolesAnd(toString(loginID), roles) + return globalLogic.HasRolesAnd(toString(loginID), roles) } // HasRolesOr 检查是否拥有任一角色(OR) func HasRolesOr(loginID interface{}, roles []string) bool { - return GetManager().HasRolesOr(toString(loginID), roles) + return globalLogic.HasRolesOr(toString(loginID), roles) } // ============ Token标签 ============ // SetTokenTag 设置Token标签 func SetTokenTag(tokenValue, tag string) error { - return GetManager().SetTokenTag(tokenValue, tag) + return globalLogic.SetTokenTag(tokenValue, tag) } // GetTokenTag 获取Token标签 func GetTokenTag(tokenValue string) (string, error) { - return GetManager().GetTokenTag(tokenValue) + return globalLogic.GetTokenTag(tokenValue) } // ============ 会话查询 ============ // GetTokenValueList 获取指定账号的所有Token func GetTokenValueList(loginID interface{}) ([]string, error) { - return GetManager().GetTokenValueListByLoginID(toString(loginID)) + return globalLogic.GetTokenValueList(loginID) } // GetSessionCount 获取指定账号的Session数量 func GetSessionCount(loginID interface{}) (int, error) { - return GetManager().GetSessionCountByLoginID(toString(loginID)) + return globalLogic.GetSessionCount(loginID) } // ============ 辅助方法 ============ @@ -288,49 +305,49 @@ func uint64ToString(u uint64) string { } func GenerateNonce() (string, error) { - if globalManager == nil { + if globalLogic == nil { panic("Manager not initialized. Call stputil.SetManager() first") } - return globalManager.GenerateNonce() + return globalLogic.GenerateNonce() } func VerifyNonce(nonce string) bool { - if globalManager == nil { + if globalLogic == nil { panic("Manager not initialized. Call stputil.SetManager() first") } - return globalManager.VerifyNonce(nonce) + return globalLogic.VerifyNonce(nonce) } func LoginWithRefreshToken(loginID interface{}, device ...string) (*security.RefreshTokenInfo, error) { - if globalManager == nil { + if globalLogic == nil { panic("Manager not initialized. Call stputil.SetManager() first") } deviceType := "default" if len(device) > 0 { deviceType = device[0] } - return globalManager.LoginWithRefreshToken(fmt.Sprintf("%v", loginID), deviceType) + return globalLogic.LoginWithRefreshToken(fmt.Sprintf("%v", loginID), deviceType) } func RefreshAccessToken(refreshToken string) (*security.RefreshTokenInfo, error) { - if globalManager == nil { + if globalLogic == nil { panic("Manager not initialized. Call stputil.SetManager() first") } - return globalManager.RefreshAccessToken(refreshToken) + return globalLogic.RefreshAccessToken(refreshToken) } func RevokeRefreshToken(refreshToken string) error { - if globalManager == nil { + if globalLogic == nil { panic("Manager not initialized. Call stputil.SetManager() first") } - return globalManager.RevokeRefreshToken(refreshToken) + return globalLogic.RevokeRefreshToken(refreshToken) } func GetOAuth2Server() *oauth2.OAuth2Server { - if globalManager == nil { + if globalLogic == nil { panic("Manager not initialized. Call stputil.SetManager() first") } - return globalManager.GetOAuth2Server() + return globalLogic.GetOAuth2Server() } // ============ Check Functions for Token-based operations | 基于Token的检查函数 ============ diff --git a/stputil/stputil_test.go b/stputil/stputil_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d3343feb956931404f1dbf5c94a3b1cd9ce0acbd --- /dev/null +++ b/stputil/stputil_test.go @@ -0,0 +1,131 @@ +package stputil + +import ( + "testing" + "time" + + "github.com/click33/sa-token-go/core/config" + "github.com/click33/sa-token-go/core/manager" + "github.com/click33/sa-token-go/storage/memory" + "github.com/stretchr/testify/assert" +) + +// setupTestManager 初始化内存存储和全局 Manager +func setupTestManager() { + storage := memory.NewStorage() + cfg := &config.Config{ + TokenName: "satoken", + Timeout: 3600, + IsConcurrent: true, + IsShare: true, + MaxLoginCount: -1, + } + mgr := manager.NewManager(storage, cfg) + SetManager(mgr) +} + +func TestLoginAndIsLogin(t *testing.T) { + setupTestManager() + + token, err := Login("user1") + assert.NoError(t, err) + assert.NotEmpty(t, token) + + assert.True(t, IsLogin(token)) + + loginID, err := GetLoginID(token) + assert.NoError(t, err) + assert.Equal(t, "user1", loginID) +} + +func TestPermissionsHelpers(t *testing.T) { + setupTestManager() + + token, err := Login("user2") + assert.NoError(t, err) + + err = SetPermissions("user2", []string{"user.read", "user.write"}) + assert.NoError(t, err) + + // HasPermission / CheckPermission + assert.True(t, HasPermission("user2", "user.read")) + assert.NoError(t, CheckPermission(token, "user.read")) + + // AND / OR helpers + assert.True(t, HasPermissionsAnd("user2", []string{"user.read", "user.write"})) + assert.True(t, HasPermissionsOr("user2", []string{"user.delete", "user.read"})) + + // Permission list by token + perms, err := GetPermissionList(token) + assert.NoError(t, err) + assert.ElementsMatch(t, []string{"user.read", "user.write"}, perms) +} + +func TestRoleHelpers(t *testing.T) { + setupTestManager() + + token, err := Login("user3") + assert.NoError(t, err) + + err = SetRoles("user3", []string{"Admin", "User"}) + assert.NoError(t, err) + + // HasRole / CheckRole + assert.True(t, HasRole("user3", "Admin")) + assert.NoError(t, CheckRole(token, "Admin")) + + // AND / OR helpers + assert.True(t, HasRolesAnd("user3", []string{"Admin", "User"})) + assert.True(t, HasRolesOr("user3", []string{"Guest", "Admin"})) + + // Role list by token + roles, err := GetRoleList(token) + assert.NoError(t, err) + assert.ElementsMatch(t, []string{"Admin", "User"}, roles) +} + +func TestDisableAndCheckDisable(t *testing.T) { + setupTestManager() + + token, err := Login("user4") + assert.NoError(t, err) + + // 初始未封禁 + assert.NoError(t, CheckDisable(token)) + + // 封禁账号 + err = Disable("user4", time.Hour) + assert.NoError(t, err) + + // 现在 CheckDisable 应返回错误(可能是“未登录”或“已封禁”等) + err = CheckDisable(token) + assert.Error(t, err) + + disabled := IsDisable("user4") + assert.True(t, disabled) +} + +func TestToStringHelpers(t *testing.T) { + assert.Equal(t, "123", toString(123)) + assert.Equal(t, "-5", toString(int(-5))) + assert.Equal(t, "0", toString(int64(0))) + assert.Equal(t, "42", toString(uint(42))) + assert.Equal(t, "", toString(struct{}{})) +} + +// TestLoginWithRefreshToken_IsLogin 验证双 Token 登录场景下,access token 能正常通过 IsLogin/CheckLogin +func TestLoginWithRefreshToken_IsLogin(t *testing.T) { + setupTestManager() + + // 使用双 token 登录 + tokenInfo, err := LoginWithRefreshToken("user-refresh", "web") + assert.NoError(t, err) + assert.NotEmpty(t, tokenInfo.AccessToken) + assert.NotEmpty(t, tokenInfo.RefreshToken) + + // 刚登录的 access token 应该是“已登录” + assert.True(t, IsLogin(tokenInfo.AccessToken)) + assert.NoError(t, CheckLogin(tokenInfo.AccessToken)) +} + +