- 数据库默认文件 filecodebox.db → fileshare.db(config.go 默认值与全部文档/编排同步)
- Go module filecodebox → fileshare(全部 import 同步,build/vet/test 全绿)
- 应用版本 APP_VERSION 2.5.6 → 26.9(health 接口已验证返回 26.9)
- deploy 编排统一:compose 项目名、Postgres 默认凭据、minio 桶名、env 注释
- JWT issuer、存储临时目录前缀、web 包名同步 fileshare
- CI:镜像 tag 以 APP_VERSION 为唯一版本源,main/tag 推送即发布
${VER} + latest;tag 触发时校验 tag 名与 APP_VERSION 一致,防错版
- 本地开发库文件已改名 fileshare.db(含 -shm/-wal 清理)
100 lines
2.8 KiB
Go
100 lines
2.8 KiB
Go
package middleware
|
||
|
||
import (
|
||
"errors"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/golang-jwt/jwt/v5"
|
||
|
||
"fileshare/internal/response"
|
||
)
|
||
|
||
// jwtClaims 自定义声明:对齐参考实现(payload 含 is_admin 与 exp)。
|
||
type jwtClaims struct {
|
||
IsAdmin bool `json:"is_admin"`
|
||
jwt.RegisteredClaims
|
||
}
|
||
|
||
// 签发/校验相关错误。
|
||
var (
|
||
ErrTokenExpired = errors.New("token已过期")
|
||
ErrTokenInvalid = errors.New("无效的签名")
|
||
ErrNotAdmin = errors.New("未授权或授权校验失败")
|
||
)
|
||
|
||
// SignAdminToken 用 HS256 签发管理员 JWT。
|
||
// secret 为数据库 settings 中的 jwt_secret;expires 为会话有效期。
|
||
func SignAdminToken(secret string, expires time.Duration) (string, time.Time, error) {
|
||
if strings.TrimSpace(secret) == "" {
|
||
return "", time.Time{}, errors.New("JWT签名密钥未初始化")
|
||
}
|
||
expiresAt := time.Now().Add(expires)
|
||
claims := jwtClaims{
|
||
IsAdmin: true,
|
||
RegisteredClaims: jwt.RegisteredClaims{
|
||
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||
Issuer: "fileshare",
|
||
},
|
||
}
|
||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||
signed, err := token.SignedString([]byte(secret))
|
||
return signed, expiresAt, err
|
||
}
|
||
|
||
// VerifyAdminToken 校验管理员 JWT:签名、过期时间与 is_admin 声明。
|
||
func VerifyAdminToken(secret, token string) (*jwtClaims, error) {
|
||
if strings.TrimSpace(secret) == "" {
|
||
return nil, errors.New("JWT签名密钥未初始化")
|
||
}
|
||
parsed, err := jwt.ParseWithClaims(token, &jwtClaims{}, func(t *jwt.Token) (any, error) {
|
||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||
return nil, ErrTokenInvalid
|
||
}
|
||
return []byte(secret), nil
|
||
}, jwt.WithValidMethods([]string{"HS256"}))
|
||
if err != nil {
|
||
if errors.Is(err, jwt.ErrTokenExpired) {
|
||
return nil, ErrTokenExpired
|
||
}
|
||
return nil, ErrTokenInvalid
|
||
}
|
||
claims, ok := parsed.Claims.(*jwtClaims)
|
||
if !ok || !parsed.Valid {
|
||
return nil, ErrTokenInvalid
|
||
}
|
||
if !claims.IsAdmin {
|
||
return nil, ErrNotAdmin
|
||
}
|
||
return claims, nil
|
||
}
|
||
|
||
// SecretProvider 动态提供当前 jwt_secret(settings KV 运行时可变)。
|
||
type SecretProvider func() string
|
||
|
||
// AdminAuth 管理员鉴权中间件:校验 Authorization: Bearer <token>。
|
||
// 成功后把声明写入 gin 上下文(ctxClaims)。
|
||
func AdminAuth(secret SecretProvider) gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
header := c.GetHeader("Authorization")
|
||
if !strings.HasPrefix(header, "Bearer ") {
|
||
response.Fail(c, 401, "未授权或授权校验失败")
|
||
return
|
||
}
|
||
token := strings.TrimSpace(strings.TrimPrefix(header, "Bearer "))
|
||
if token == "" {
|
||
response.Fail(c, 401, "未授权或授权校验失败")
|
||
return
|
||
}
|
||
claims, err := VerifyAdminToken(secret(), token)
|
||
if err != nil {
|
||
response.Fail(c, 401, err.Error())
|
||
return
|
||
}
|
||
c.Set("claims", claims)
|
||
c.Next()
|
||
}
|
||
}
|