This commit is contained in:
2026-08-14 16:34:17 +08:00
parent e0b0e1d8c8
commit f63acae353
53 changed files with 2488 additions and 350 deletions
+44 -12
View File
@@ -1,6 +1,7 @@
package middleware
import (
"errors"
"net/http"
"strconv"
"strings"
@@ -12,16 +13,28 @@ import (
"kra/pkg/adminauth"
"github.com/gin-gonic/gin"
"golang.org/x/sync/singleflight"
)
const claimsKey = "admin_claims"
var refreshTokens singleflight.Group
type refreshedToken struct {
token string
expiresAt int64
}
func Auth(runtime *conf.Runtime, settings *service.SettingsService) gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("x-token")
if token == "" {
token, _ = c.Cookie("x-token")
}
if token == "" {
httpx.NoAuth(c, "未登录或非法访问,请登录")
return
}
secret := ""
config := runtime.Admin()
if config != nil && config.Jwt != nil {
@@ -29,15 +42,24 @@ func Auth(runtime *conf.Runtime, settings *service.SettingsService) gin.HandlerF
}
claims, err := adminauth.Parse(token, secret)
if err != nil {
httpx.NoAuth(c, "未登录或非法访问")
message := "无法处理此token"
switch {
case errors.Is(err, adminauth.ErrTokenExpired):
message = "登录已过期,请重新登录"
case errors.Is(err, adminauth.ErrTokenMalformed):
message = "这不是一个token"
case errors.Is(err, adminauth.ErrTokenSignatureInvalid):
message = "无效签名"
case errors.Is(err, adminauth.ErrTokenNotValidYet):
message = "token尚未激活"
}
httpx.SetTokenCookie(c, "", -1)
httpx.NoAuth(c, message)
return
}
if disabled, checkErr := settings.IsTokenDisabled(c.Request.Context(), token); checkErr != nil || disabled {
httpx.NoAuth(c, "登录状态已失效")
return
}
if active, checkErr := settings.ActiveTokenMatches(c.Request.Context(), claims.Username, token); checkErr != nil || !active {
httpx.NoAuth(c, "登录状态已失效")
httpx.SetTokenCookie(c, "", -1)
httpx.NoAuth(c, "您的帐户异地登陆或令牌失效")
return
}
if claims.ExpiresAt != nil && claims.BufferTime > 0 && time.Until(claims.ExpiresAt.Time) < time.Duration(claims.BufferTime)*time.Second {
@@ -55,11 +77,21 @@ func Auth(runtime *conf.Runtime, settings *service.SettingsService) gin.HandlerF
issuer = config.Jwt.Issuer
}
}
newToken, newClaims, refreshErr := adminauth.Generate(secret, issuer, expires, buffer, claims.ID, claims.AuthorityID, claims.UUID, claims.Username, claims.NickName, claims.MustChangePwd)
if refreshErr == nil && settings.RotateActiveToken(c.Request.Context(), claims.Username, token, newToken, expires) == nil {
c.Header("new-token", newToken)
c.Header("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt.Unix(), 10))
httpx.SetTokenCookie(c, newToken, int(expires.Seconds()))
value, refreshErr, _ := refreshTokens.Do(token, func() (any, error) {
newToken, newClaims, generateErr := adminauth.Generate(secret, issuer, expires, buffer, claims.ID, claims.AuthorityID, claims.UUID, claims.Username, claims.NickName, claims.MustChangePwd)
if generateErr != nil {
return nil, generateErr
}
if rotateErr := settings.RotateActiveToken(c.Request.Context(), claims.Username, token, newToken, expires); rotateErr != nil {
return nil, rotateErr
}
return refreshedToken{token: newToken, expiresAt: newClaims.ExpiresAt.Unix()}, nil
})
if refreshErr == nil {
refreshed := value.(refreshedToken)
c.Header("new-token", refreshed.token)
c.Header("new-expires-at", strconv.FormatInt(refreshed.expiresAt, 10))
httpx.SetTokenCookie(c, refreshed.token, int(expires.Seconds()))
}
}
c.Set(claimsKey, claims)
@@ -85,6 +117,6 @@ func MustChangePassword() gin.HandlerFunc {
c.Next()
return
}
c.AbortWithStatusJSON(http.StatusConflict, httpx.Response{Code: httpx.CodePasswordChangeRequired, Data: gin.H{"needChangePassword": true}, Msg: "请先修改初始密码"})
c.AbortWithStatusJSON(http.StatusConflict, httpx.Response{Code: httpx.CodePasswordChangeRequired, Data: gin.H{"needChangePassword": true}, Msg: "密码已过期,请先修改密码"})
}
}