认证方法
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()
})