Files
nl-blogs/server/middleware/auth.go
2026-01-15 13:51:44 +08:00

210 lines
4.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package middleware
import (
"errors"
"net/http"
"os"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"github.com/niangaodev/art-code/repositories"
)
// JWT密钥
var jwtSecret = []byte(getJWTSecret())
// getJWTSecret 获取JWT密钥优先从环境变量获取否则使用默认值
func getJWTSecret() string {
secret := os.Getenv("JWT_SECRET")
if secret == "" {
return "your-secret-key" // 开发环境默认值,生产环境必须通过环境变量设置
}
return secret
}
// Claims JWT声明结构
type Claims struct {
UserID uint `json:"userId"`
Username string `json:"username"`
Role string `json:"role"`
RoleID uint `json:"roleId"`
jwt.RegisteredClaims
}
// GenerateToken 生成JWT令牌
func GenerateToken(userID uint, username, role string) (string, int64, error) {
// 设置令牌过期时间为7天
expireTime := time.Now().Add(7 * 24 * time.Hour)
expireUnix := expireTime.Unix()
// 获取用户的RoleID
var roleID uint
userRole, err := repositories.GetRoleByName(role)
if err == nil && userRole != nil {
roleID = userRole.ID
}
// 创建声明
claims := Claims{
UserID: userID,
Username: username,
Role: role,
RoleID: roleID,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expireTime),
IssuedAt: jwt.NewNumericDate(time.Now()),
Subject: "admin-token",
},
}
// 创建令牌
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 签名令牌
tokenString, err := token.SignedString(jwtSecret)
if err != nil {
return "", 0, err
}
return tokenString, expireUnix, nil
}
// ParseToken 解析JWT令牌
func ParseToken(tokenString string) (*Claims, error) {
// 解析令牌
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if err != nil {
return nil, err
}
// 验证令牌
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("invalid token")
}
// AuthMiddleware JWT认证中间件
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 从请求头中获取令牌
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header is required"})
c.Abort()
return
}
// 检查令牌格式
parts := strings.SplitN(authHeader, " ", 2)
if !(len(parts) == 2 && parts[0] == "Bearer") {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header format must be Bearer {token}"})
c.Abort()
return
}
// 解析令牌
claims, err := ParseToken(parts[1])
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or expired token"})
c.Abort()
return
}
// 将用户信息存储到上下文
c.Set("userID", claims.UserID)
c.Set("username", claims.Username)
c.Set("role", claims.Role)
c.Set("roleID", claims.RoleID)
c.Next()
}
}
// RoleMiddleware 角色验证中间件 (Legacy support)
func RoleMiddleware(roles ...string) gin.HandlerFunc {
return func(c *gin.Context) {
// 从上下文中获取用户角色
role, exists := c.Get("role")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
c.Abort()
return
}
// 检查用户角色是否在允许列表中
allowed := false
for _, r := range roles {
if role == r {
allowed = true
break
}
}
if !allowed {
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
c.Abort()
return
}
c.Next()
}
}
// PermissionMiddleware 权限验证中间件
func PermissionMiddleware(resource, action string) gin.HandlerFunc {
return func(c *gin.Context) {
// 获取用户RoleID
roleIDInterface, exists := c.Get("roleID")
if !exists {
// 尝试从role name获取
roleName, exists := c.Get("role")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
c.Abort()
return
}
role, err := repositories.GetRoleByName(roleName.(string))
if err != nil || role == nil {
c.JSON(http.StatusForbidden, gin.H{"error": "Role not found"})
c.Abort()
return
}
roleIDInterface = role.ID
}
roleID := roleIDInterface.(uint)
// 获取该角色的所有权限
permissions, err := repositories.GetPermissionsByRoleID(roleID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check permissions"})
c.Abort()
return
}
// 检查是否有匹配的权限
allowed := false
for _, p := range permissions {
if p.Resource == resource && p.Action == action {
allowed = true
break
}
}
if !allowed {
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
c.Abort()
return
}
c.Next()
}
}