Skip to content

认证方法

VEF Framework 支持多种认证方式,包括 JWT、OpenAPI 签名和密码认证。

JWT 认证

配置 JWT

yaml
# config.yaml
auth:
  jwt:
    secret: "your-secret-key"
    expiration: 24h
    refreshExpiration: 168h
    issuer: "vef-app"

生成 Token

go
import "github.com/ilxqx/vef-framework-go/auth"

// Generate access token
token, err := auth.GenerateToken(auth.TokenClaims{
    UserId:   user.Id,
    Username: user.Username,
    Roles:    []string{"admin", "user"},
})

// Generate refresh token
refreshToken, err := auth.GenerateRefreshToken(user.Id)

验证 Token

go
// Token is automatically validated by middleware
// Access user info in handler
func (r *UserResource) GetProfile(ctx fiber.Ctx) error {
    currentUser := contextx.GetCurrentUser(ctx)
    // currentUser contains UserId, Username, Roles, etc.
    return api.Success(ctx, currentUser)
}

OpenAPI 签名认证

配置签名认证

yaml
# config.yaml
auth:
  openapi:
    enabled: true
    signatureHeader: "X-Signature"
    timestampHeader: "X-Timestamp"
    nonceHeader: "X-Nonce"
    tolerance: 300 # seconds

签名算法

go
// Client-side signature generation
func generateSignature(appKey, appSecret, timestamp, nonce, body string) string {
    message := fmt.Sprintf("%s%s%s%s", appKey, timestamp, nonce, body)
    h := hmac.New(sha256.New, []byte(appSecret))
    h.Write([]byte(message))
    return hex.EncodeToString(h.Sum(nil))
}

密码认证

实现 UserLoader

go
package auth

import (
    "my-app/internal/modules/user/models"
    
    "github.com/ilxqx/vef-framework-go/auth"
    "github.com/ilxqx/vef-framework-go/orm"
    "golang.org/x/crypto/bcrypt"
)

type UserLoaderImpl struct{}

func NewUserLoader() auth.UserLoader {
    return &UserLoaderImpl{}
}

// LoadByUsername loads user by username for authentication
func (l *UserLoaderImpl) LoadByUsername(username string) (*auth.UserInfo, error) {
    var user models.User
    
    err := orm.DB().
        Preload("Roles").
        Where("username = ? AND is_active = ?", username, true).
        First(&user).Error
    
    if err != nil {
        return nil, err
    }
    
    // Extract role IDs
    roleIds := make([]string, len(user.Roles))
    for i, role := range user.Roles {
        roleIds[i] = role.Id
    }
    
    return &auth.UserInfo{
        Id:           user.Id,
        Username:     user.Username,
        Password:     user.Password, // Hashed password
        RoleIds:      roleIds,
        DepartmentId: user.DepartmentId,
        TenantId:     user.TenantId,
        Extra: map[string]interface{}{
            "email": user.Email,
            "phone": user.Phone,
        },
    }, nil
}

// LoadById loads user by ID
func (l *UserLoaderImpl) LoadById(userId string) (*auth.UserInfo, error) {
    var user models.User
    
    err := orm.DB().
        Preload("Roles").
        First(&user, "id = ?", userId).Error
    
    if err != nil {
        return nil, err
    }
    
    roleIds := make([]string, len(user.Roles))
    for i, role := range user.Roles {
        roleIds[i] = role.Id
    }
    
    return &auth.UserInfo{
        Id:           user.Id,
        Username:     user.Username,
        RoleIds:      roleIds,
        DepartmentId: user.DepartmentId,
        TenantId:     user.TenantId,
    }, nil
}

注册 UserLoader

go
// In your module setup
func NewAuthModule() fx.Option {
    return fx.Options(
        fx.Provide(
            auth.NewUserLoader,
        ),
    )
}

登录 API

go
package resources

import (
    "github.com/ilxqx/vef-framework-go/api"
    "github.com/ilxqx/vef-framework-go/auth"
    "github.com/gofiber/fiber/v2"
    "golang.org/x/crypto/bcrypt"
)

type AuthResource struct {
    api.Resource
    userLoader auth.UserLoader
}

func NewAuthResource(userLoader auth.UserLoader) api.Resource {
    resource := &AuthResource{
        Resource:   api.NewResource("auth"),
        userLoader: userLoader,
    }
    
    resource.RegisterApi("login", resource.Login)
    resource.RegisterApi("refresh", resource.RefreshToken)
    
    return resource
}

func (r *AuthResource) Login(ctx fiber.Ctx, params struct {
    Username string `json:"username" validate:"required"`
    Password string `json:"password" validate:"required"`
}) error {
    // Load user
    userInfo, err := r.userLoader.LoadByUsername(params.Username)
    if err != nil {
        return api.Error(ctx, "Invalid username or password")
    }
    
    // Verify password
    if err := bcrypt.CompareHashAndPassword(
        []byte(userInfo.Password),
        []byte(params.Password),
    ); err != nil {
        return api.Error(ctx, "Invalid username or password")
    }
    
    // Generate tokens
    accessToken, _ := auth.GenerateToken(auth.TokenClaims{
        UserId:   userInfo.Id,
        Username: userInfo.Username,
        Roles:    userInfo.RoleIds,
    })
    
    refreshToken, _ := auth.GenerateRefreshToken(userInfo.Id)
    
    return api.Success(ctx, map[string]interface{}{
        "accessToken":  accessToken,
        "refreshToken": refreshToken,
        "expiresIn":    86400, // 24 hours
    })
}

func (r *AuthResource) RefreshToken(ctx fiber.Ctx, params struct {
    RefreshToken string `json:"refreshToken" validate:"required"`
}) error {
    // Validate refresh token
    userId, err := auth.ValidateRefreshToken(params.RefreshToken)
    if err != nil {
        return api.Error(ctx, "Invalid refresh token")
    }
    
    // Load user
    userInfo, err := r.userLoader.LoadById(userId)
    if err != nil {
        return api.Error(ctx, "User not found")
    }
    
    // Generate new access token
    accessToken, _ := auth.GenerateToken(auth.TokenClaims{
        UserId:   userInfo.Id,
        Username: userInfo.Username,
        Roles:    userInfo.RoleIds,
    })
    
    return api.Success(ctx, map[string]interface{}{
        "accessToken": accessToken,
        "expiresIn":   86400,
    })
}

认证中间件

配置认证中间件

go
import "github.com/ilxqx/vef-framework-go/middleware"

app.Use(middleware.Auth(middleware.AuthConfig{
    // Skip authentication for these paths
    SkipPaths: []string{
        "/api/auth/login",
        "/api/auth/register",
        "/health",
    },
    
    // Custom token extractor
    TokenExtractor: func(ctx fiber.Ctx) string {
        // Default: Bearer token from Authorization header
        return middleware.DefaultTokenExtractor(ctx)
    },
}))

跳过认证

go
// Skip authentication for specific API
apis.NewFindAllApi[models.Product, payloads.ProductSearch]().
    WithSkipAuth(true)

多租户认证

go
// Extract tenant from subdomain or header
app.Use(func(ctx fiber.Ctx) error {
    tenantId := ctx.Get("X-Tenant-Id")
    if tenantId == "" {
        // Extract from subdomain
        host := ctx.Hostname()
        parts := strings.Split(host, ".")
        if len(parts) > 2 {
            tenantId = parts[0]
        }
    }
    
    ctx.Locals("tenantId", tenantId)
    return ctx.Next()
})

下一步

基于 Apache License 2.0 许可发布