mirror of
https://github.com/skyle1995/NetworkAuth.git
synced 2026-05-25 02:24:05 +08:00
修复 鉴权令牌刷新
This commit is contained in:
@@ -189,6 +189,82 @@ func LogoutHandler(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RefreshTokenHandler 刷新管理员会话令牌
|
||||||
|
// - 校验当前会话(Cookie 或 Authorization)
|
||||||
|
// - 重新签发 JWT 并同步写回 Cookie
|
||||||
|
// - 返回前端可直接持久化的新 token 信息
|
||||||
|
func RefreshTokenHandler(c *gin.Context) {
|
||||||
|
token, err := getJWTCookie(c)
|
||||||
|
if err != nil || token == "" {
|
||||||
|
clearInvalidJWTCookie(c)
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "未登录或会话已过期",
|
||||||
|
"data": nil,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, err := parseJWTToken(token)
|
||||||
|
if err != nil {
|
||||||
|
clearInvalidJWTCookie(c)
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "无效的会话信息",
|
||||||
|
"data": nil,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !validateAdminPasswordHash(claims, c) {
|
||||||
|
clearInvalidJWTCookie(c)
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "会话已失效,请重新登录",
|
||||||
|
"data": nil,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := database.GetDB()
|
||||||
|
if err != nil {
|
||||||
|
authBaseController.HandleInternalError(c, "数据库连接失败", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var adminUser models.User
|
||||||
|
if err := db.Where("uuid = ?", claims.UUID).First(&adminUser).Error; err != nil {
|
||||||
|
clearInvalidJWTCookie(c)
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"code": 1,
|
||||||
|
"msg": "会话已失效,请重新登录",
|
||||||
|
"data": nil,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newToken, err := generateJWTTokenForAdmin(adminUser.Username, adminUser.Password, adminUser.UUID, adminUser.Role)
|
||||||
|
if err != nil {
|
||||||
|
authBaseController.HandleInternalError(c, "生成令牌失败", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
secure, sameSite, domain, maxAge := services.GetSettingsService().GetCookieConfig()
|
||||||
|
cookieObj := utils.CreateSecureCookie("admin_session", newToken, maxAge, domain, secure, sameSite)
|
||||||
|
c.SetCookie(cookieObj.Name, cookieObj.Value, cookieObj.MaxAge, cookieObj.Path, cookieObj.Domain, cookieObj.Secure, cookieObj.HttpOnly)
|
||||||
|
|
||||||
|
expireHours := services.GetSettingsService().GetJWTExpire()
|
||||||
|
if expireHours <= 0 {
|
||||||
|
expireHours = 24
|
||||||
|
}
|
||||||
|
|
||||||
|
authBaseController.HandleSuccess(c, "刷新成功", gin.H{
|
||||||
|
"accessToken": newToken,
|
||||||
|
"refreshToken": newToken,
|
||||||
|
"expires": time.Now().Add(time.Duration(expireHours) * time.Hour),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// CSRF 相关辅助函数
|
// CSRF 相关辅助函数
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ func RegisterAdminRoutes(rg *gin.RouterGroup) {
|
|||||||
admin.GET("/captcha", adminctl.CaptchaHandler)
|
admin.GET("/captcha", adminctl.CaptchaHandler)
|
||||||
admin.GET("/csrf", adminctl.CSRFTokenHandler)
|
admin.GET("/csrf", adminctl.CSRFTokenHandler)
|
||||||
admin.POST("/login", adminctl.LoginHandler)
|
admin.POST("/login", adminctl.LoginHandler)
|
||||||
|
admin.POST("/refresh-token", adminctl.RefreshTokenHandler)
|
||||||
|
|
||||||
// 公开设置API
|
// 公开设置API
|
||||||
admin.GET("/settings/public", adminctl.SettingsPublicHandler)
|
admin.GET("/settings/public", adminctl.SettingsPublicHandler)
|
||||||
|
|||||||
@@ -3,28 +3,25 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetRootDir 获取当前程序运行的真实根目录
|
// GetRootDir 获取当前程序运行的真实根目录
|
||||||
// 能够智能、跨平台地识别是编译后的可执行文件运行,还是通过 `go run` 运行(通常在临时目录下)
|
// 能跨平台地识别是在生产环境执行二进制文件,还是在开发阶段使用 go run/test 乃至 IDE 调试运行
|
||||||
func GetRootDir() string {
|
func GetRootDir() string {
|
||||||
var baseDir string
|
var baseDir string
|
||||||
|
|
||||||
// 首先尝试获取当前工作目录
|
|
||||||
workDir, err := os.Getwd()
|
workDir, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
workDir = "."
|
workDir = "."
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取程序可执行文件所在目录
|
|
||||||
execPath, err := os.Executable()
|
execPath, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 如果获取可执行文件路径失败,使用当前工作目录
|
|
||||||
return workDir
|
return workDir
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析软链接,获取真实物理路径(macOS 下 /tmp 经常是 /private/tmp 的软链)
|
|
||||||
realExecPath, err := filepath.EvalSymlinks(execPath)
|
realExecPath, err := filepath.EvalSymlinks(execPath)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
execPath = realExecPath
|
execPath = realExecPath
|
||||||
@@ -36,27 +33,23 @@ func GetRootDir() string {
|
|||||||
realTempDir = os.TempDir()
|
realTempDir = os.TempDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 跨平台安全地判断 execDir 是否在 realTempDir 内部
|
|
||||||
// 使用 filepath.Rel 可以避免直接 HasPrefix 带来的大小写、路径分隔符以及部分目录名重合的问题
|
|
||||||
rel, err := filepath.Rel(realTempDir, execDir)
|
|
||||||
isGoRun := false
|
isGoRun := false
|
||||||
if err == nil {
|
rel, err := filepath.Rel(realTempDir, execDir)
|
||||||
// 如果 rel 不以 ".." 开头,说明 execDir 在 TempDir 内部,即为 go run 模式
|
if err == nil && rel != ".." && !strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
||||||
if rel != ".." && !strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
isGoRun = true
|
||||||
isGoRun = true
|
} else if strings.Contains(execPath, "go-build") || strings.Contains(execPath, "__debug_bin") || strings.HasSuffix(execPath, ".test") {
|
||||||
}
|
isGoRun = true
|
||||||
} else {
|
} else if strings.Contains(filepath.Base(execPath), "___go_build") || strings.HasPrefix(filepath.Base(execPath), "dlv") {
|
||||||
// fallback: 如果 Rel 失败(例如跨盘符),则退回简单的 HasPrefix 判断(带上分隔符防误判)
|
isGoRun = true
|
||||||
cleanTemp := filepath.Clean(realTempDir) + string(os.PathSeparator)
|
|
||||||
cleanExec := filepath.Clean(execDir) + string(os.PathSeparator)
|
|
||||||
if strings.HasPrefix(strings.ToLower(cleanExec), strings.ToLower(cleanTemp)) {
|
|
||||||
isGoRun = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if isGoRun {
|
if isGoRun {
|
||||||
baseDir = workDir
|
// 开发模式下,利用 runtime 获取 utils/path.go 所在的绝对路径
|
||||||
|
// 向上两级即可得到项目的真实绝对根目录,避免因终端 CWD 不同导致的配置读取和连接失败
|
||||||
|
_, b, _, _ := runtime.Caller(0)
|
||||||
|
baseDir = filepath.Dir(filepath.Dir(b))
|
||||||
} else {
|
} else {
|
||||||
|
// 生产模式下(正式编译的独立二进制),返回可执行文件所在目录
|
||||||
baseDir = execDir
|
baseDir = execDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user