mirror of
https://github.com/click33/sa-token-go.git
synced 2025-12-24 13:48:04 +08:00
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:
@@ -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
|
||||
|
||||
@@ -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) - 事件系统详解
|
||||
|
||||
@@ -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
192
core/router/router.go
Normal 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()
|
||||
}
|
||||
|
||||
@@ -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
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
551
docs/guide/path-auth.md
Normal 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
551
docs/guide/path-auth_zh.md
Normal 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` - 创建路径不允许访问错误
|
||||
@@ -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)
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user