chore: upgrade version to v0.1.6

- Add path-based authentication feature
- Support Ant-style wildcard patterns
- Integrate path auth into all framework integrations
- Update documentation with detailed usage examples
This commit is contained in:
c
2025-12-17 13:18:33 +07:00
parent bd657de828
commit 7f46eaf7b0
29 changed files with 1543 additions and 44 deletions

View File

@@ -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
@@ -105,7 +106,7 @@ func init() {
___/ / /_/ / / / / /_/ / ,< / __/ / / /_____/ /_/ / /_/ /
/____/\__,_/ /_/ \____/_/|_|\___/_/ /_/ \____/\____/
:: Sa-Token-Go :: (v0.1.5)
:: Sa-Token-Go :: (v0.1.6)
:: Go Version :: go1.21.0
:: GOOS/GOARCH :: linux/amd64
@@ -575,6 +576,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

View File

@@ -11,6 +11,7 @@
- 🔐 **登录认证** - 支持多设备登录、Token管理
- 🛡️ **权限验证** - 细粒度权限控制、通配符支持(`*`, `user:*`, `user:*:view`
- 🛣️ **路径鉴权** - 灵活的路径鉴权、支持Ant风格通配符
- 👥 **角色管理** - 灵活的角色授权机制
- 🚫 **账号封禁** - 临时/永久封禁功能
- 👢 **踢人下线** - 强制用户下线、多端互斥登录
@@ -105,7 +106,7 @@ func init() {
___/ / /_/ / / / / /_/ / ,< / __/ / / /_____/ /_/ / /_/ /
/____/\__,_/ /_/ \____/_/|_|\___/_/ /_/ \____/\____/
:: Sa-Token-Go :: (v0.1.5)
:: Sa-Token-Go :: (v0.1.6)
:: Go Version :: go1.21.0
:: GOOS/GOARCH :: linux/amd64
@@ -583,6 +584,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) - 事件系统详解

View File

@@ -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 | 服务器内部错误

192
core/router/router.go Normal file
View File

@@ -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()
}

View File

@@ -10,6 +10,7 @@ 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"
@@ -58,6 +59,8 @@ type (
OAuth2Client = oauth2.Client
OAuth2AccessToken = oauth2.AccessToken
OAuth2GrantType = oauth2.GrantType
PathAuthConfig = router.PathAuthConfig
AuthResult = router.AuthResult
)
// Adapter interfaces | 适配器接口
@@ -120,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

BIN
docs/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 249 KiB

551
docs/guide/path-auth.md Normal file
View File

@@ -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

551
docs/guide/path-auth_zh.md Normal file
View File

@@ -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` - 创建路径不允许访问错误

View File

@@ -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)

View File

@@ -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.6
github.com/click33/sa-token-go/stputil v0.1.6
)
require (

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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.6
github.com/click33/sa-token-go/stputil v0.1.6
github.com/labstack/echo/v4 v4.11.4
)

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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.6
github.com/click33/sa-token-go/stputil v0.1.6
github.com/gofiber/fiber/v2 v2.52.0
)

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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.6
github.com/click33/sa-token-go/stputil v0.1.6
github.com/gogf/gf/v2 v2.9.4
)

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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.6
github.com/click33/sa-token-go/stputil v0.1.6
github.com/gin-gonic/gin v1.10.0
github.com/stretchr/testify v1.11.1
)

View File

@@ -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) {

View File

@@ -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.6
github.com/click33/sa-token-go/storage/memory v0.1.6
github.com/click33/sa-token-go/stputil v0.1.6
github.com/go-kratos/kratos/v2 v2.9.1
)

View File

@@ -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

View File

@@ -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.6
replace github.com/click33/sa-token-go/core => ../../core

View File

@@ -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.6
github.com/redis/go-redis/v9 v9.5.1
)

View File

@@ -2,7 +2,7 @@ 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.6
require (
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect