mirror of
https://github.com/skyle1995/NetworkAuth.git
synced 2026-05-25 02:24:05 +08:00
Fix the new authentication issue
This commit is contained in:
@@ -185,13 +185,15 @@ func clearInvalidJWTCookie(w http.ResponseWriter) {
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
|
||||
// JWT密钥(生产环境应从配置文件或环境变量读取)
|
||||
var jwtSecret = []byte(viper.GetString("security.jwt_secret"))
|
||||
// getJWTSecret 动态获取当前的JWT密钥
|
||||
// 修复安全漏洞:确保每次都从最新配置中获取密钥,而不是使用启动时的全局变量
|
||||
func getJWTSecret() []byte {
|
||||
return []byte(viper.GetString("security.jwt_secret"))
|
||||
}
|
||||
|
||||
// JWTClaims JWT载荷结构
|
||||
type JWTClaims struct {
|
||||
Username string `json:"username"`
|
||||
IsAdmin bool `json:"is_admin"` // 是否为管理员
|
||||
PasswordHash string `json:"password_hash"` // 密码哈希摘要,用于验证密码是否被修改
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
@@ -206,7 +208,6 @@ func generateJWTTokenForAdmin(adminUser models.User) (string, error) {
|
||||
|
||||
claims := JWTClaims{
|
||||
Username: adminUser.Username,
|
||||
IsAdmin: true, // 管理员
|
||||
PasswordHash: passwordHashDigest, // 包含密码哈希摘要
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
|
||||
@@ -218,7 +219,7 @@ func generateJWTTokenForAdmin(adminUser models.User) (string, error) {
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString(jwtSecret)
|
||||
return token.SignedString(getJWTSecret())
|
||||
}
|
||||
|
||||
// parseJWTToken 解析并验证JWT令牌
|
||||
@@ -230,7 +231,7 @@ func parseJWTToken(tokenString string) (*JWTClaims, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return jwtSecret, nil
|
||||
return getJWTSecret(), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -244,11 +245,48 @@ func parseJWTToken(tokenString string) (*JWTClaims, error) {
|
||||
return nil, fmt.Errorf("invalid token")
|
||||
}
|
||||
|
||||
// getJWTCookie 获取JWT cookie的通用函数
|
||||
func getJWTCookie(r *http.Request) (*http.Cookie, error) {
|
||||
return r.Cookie("admin_session")
|
||||
}
|
||||
|
||||
// validateAdminPasswordHash 验证管理员密码哈希的通用函数
|
||||
func validateAdminPasswordHash(claims *JWTClaims, r *http.Request) bool {
|
||||
// 【安全修复】验证数据库中的当前密码哈希
|
||||
// 这确保了密码修改后,旧的JWT令牌会失效
|
||||
db, err := database.GetDB()
|
||||
if err != nil {
|
||||
fmt.Printf("[SECURITY WARNING] Database connection failed during auth - Username=%s, IP=%s\n",
|
||||
claims.Username, r.RemoteAddr)
|
||||
return false
|
||||
}
|
||||
|
||||
// 获取当前数据库中的管理员密码
|
||||
var adminPassword models.Settings
|
||||
if err := db.Where("name = ?", "admin_password").First(&adminPassword).Error; err != nil {
|
||||
fmt.Printf("[SECURITY WARNING] Admin password not found in database - Username=%s, IP=%s\n",
|
||||
claims.Username, r.RemoteAddr)
|
||||
return false
|
||||
}
|
||||
|
||||
// 生成当前数据库密码的哈希摘要
|
||||
currentPasswordHash := utils.GenerateSHA256Hash(adminPassword.Value)
|
||||
|
||||
// 验证JWT中的密码哈希是否与当前数据库中的密码哈希一致
|
||||
if claims.PasswordHash != currentPasswordHash {
|
||||
fmt.Printf("[SECURITY WARNING] Password hash mismatch - JWT token invalidated - Username=%s, IP=%s\n",
|
||||
claims.Username, r.RemoteAddr)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsAdminAuthenticated 判断管理员是否已认证(导出)
|
||||
// - 检查admin_session Cookie中的JWT令牌
|
||||
// - 验证令牌签名、过期时间和用户角色
|
||||
func IsAdminAuthenticated(r *http.Request) bool {
|
||||
cookie, err := r.Cookie("admin_session")
|
||||
cookie, err := getJWTCookie(r)
|
||||
if err != nil || cookie.Value == "" {
|
||||
return false
|
||||
}
|
||||
@@ -259,27 +297,17 @@ func IsAdminAuthenticated(r *http.Request) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// 验证用户角色(只允许管理员)
|
||||
if !claims.IsAdmin {
|
||||
return false
|
||||
}
|
||||
// 注释:由于这是管理员专用认证函数,不需要额外的角色验证
|
||||
|
||||
// 对于管理员,不需要验证数据库中的用户记录,因为管理员信息存储在settings中
|
||||
// 只需要验证JWT中的信息即可
|
||||
if !claims.IsAdmin {
|
||||
fmt.Printf("[SECURITY WARNING] Invalid admin token detected - Username=%s, IP=%s\n",
|
||||
claims.Username, r.RemoteAddr)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
// 验证密码哈希
|
||||
return validateAdminPasswordHash(claims, r)
|
||||
}
|
||||
|
||||
// IsAdminAuthenticatedWithCleanup 带自动清理功能的JWT校验函数
|
||||
// - 当JWT校验失败时,自动清理失效的Cookie
|
||||
// - 适用于API接口等需要清理失效令牌的场景
|
||||
func IsAdminAuthenticatedWithCleanup(w http.ResponseWriter, r *http.Request) bool {
|
||||
cookie, err := r.Cookie("admin_session")
|
||||
cookie, err := getJWTCookie(r)
|
||||
if err != nil || cookie.Value == "" {
|
||||
return false
|
||||
}
|
||||
@@ -292,17 +320,10 @@ func IsAdminAuthenticatedWithCleanup(w http.ResponseWriter, r *http.Request) boo
|
||||
return false
|
||||
}
|
||||
|
||||
// 验证用户角色(只允许管理员)
|
||||
if !claims.IsAdmin {
|
||||
clearInvalidJWTCookie(w)
|
||||
return false
|
||||
}
|
||||
// 注释:由于这是管理员专用认证函数,不需要额外的角色验证
|
||||
|
||||
// 对于管理员,不需要验证数据库中的用户记录,因为管理员信息存储在settings中
|
||||
// 只需要验证JWT中的信息即可
|
||||
if !claims.IsAdmin {
|
||||
fmt.Printf("[SECURITY WARNING] Invalid admin token detected - Username=%s, IP=%s\n",
|
||||
claims.Username, r.RemoteAddr)
|
||||
// 验证密码哈希
|
||||
if !validateAdminPasswordHash(claims, r) {
|
||||
clearInvalidJWTCookie(w)
|
||||
return false
|
||||
}
|
||||
@@ -315,7 +336,7 @@ func IsAdminAuthenticatedWithCleanup(w http.ResponseWriter, r *http.Request) boo
|
||||
// - 自动刷新接近过期的令牌(剩余时间少于6小时时刷新)
|
||||
// - 返回用户ID、用户名和角色
|
||||
func GetCurrentAdminUser(r *http.Request) (*JWTClaims, error) {
|
||||
cookie, err := r.Cookie("admin_session")
|
||||
cookie, err := getJWTCookie(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("未找到会话信息")
|
||||
}
|
||||
@@ -325,15 +346,7 @@ func GetCurrentAdminUser(r *http.Request) (*JWTClaims, error) {
|
||||
return nil, fmt.Errorf("无效的会话信息")
|
||||
}
|
||||
|
||||
if !claims.IsAdmin {
|
||||
return nil, fmt.Errorf("权限不足")
|
||||
}
|
||||
|
||||
// 对于管理员,不需要验证数据库中的用户记录,因为管理员信息存储在settings中
|
||||
// 只需要验证JWT中的信息即可
|
||||
if !claims.IsAdmin {
|
||||
return nil, fmt.Errorf("无效的管理员令牌")
|
||||
}
|
||||
// 注释:由于这是管理员专用函数,不需要额外的角色验证
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
@@ -343,7 +356,7 @@ func GetCurrentAdminUser(r *http.Request) (*JWTClaims, error) {
|
||||
// - 自动刷新接近过期的令牌(剩余时间少于6小时时刷新)
|
||||
// - 返回用户ID、用户名、角色和是否刷新了令牌
|
||||
func GetCurrentAdminUserWithRefresh(w http.ResponseWriter, r *http.Request) (*JWTClaims, bool, error) {
|
||||
cookie, err := r.Cookie("admin_session")
|
||||
cookie, err := getJWTCookie(r)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("未找到会话信息")
|
||||
}
|
||||
@@ -353,19 +366,16 @@ func GetCurrentAdminUserWithRefresh(w http.ResponseWriter, r *http.Request) (*JW
|
||||
return nil, false, fmt.Errorf("无效的会话信息")
|
||||
}
|
||||
|
||||
if !claims.IsAdmin {
|
||||
return nil, false, fmt.Errorf("权限不足")
|
||||
}
|
||||
// 注释:由于这是管理员专用函数,不需要额外的角色验证
|
||||
|
||||
// 对于管理员,不需要验证数据库中的用户记录,因为管理员信息存储在settings中
|
||||
// 只需要验证JWT中的信息即可
|
||||
if !claims.IsAdmin {
|
||||
return nil, false, fmt.Errorf("无效的管理员令牌")
|
||||
// 验证密码哈希
|
||||
if !validateAdminPasswordHash(claims, r) {
|
||||
return nil, false, fmt.Errorf("会话已失效,请重新登录")
|
||||
}
|
||||
|
||||
// 检查是否需要刷新令牌(根据配置的阈值)
|
||||
refreshed := false
|
||||
refreshThreshold := time.Duration(viper.GetInt("security.jwt_refresh_threshold_hours")) * time.Hour
|
||||
refreshThreshold := time.Duration(viper.GetInt("security.jwt_refresh")) * time.Hour
|
||||
if time.Until(claims.ExpiresAt.Time) < refreshThreshold {
|
||||
// 为管理员生成新的JWT令牌
|
||||
adminUser := models.User{
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"networkDev/utils"
|
||||
|
||||
"github.com/mojocn/base64Captcha"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// 全局验证码存储器
|
||||
@@ -91,6 +92,11 @@ func CaptchaHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// 这个函数将在登录处理中被调用
|
||||
// 支持大小写不敏感匹配
|
||||
func VerifyCaptcha(r *http.Request, captchaValue string) bool {
|
||||
// 检查是否为开发模式,如果是则跳过验证码验证
|
||||
if viper.GetBool("server.dev_mode") {
|
||||
return true
|
||||
}
|
||||
|
||||
// 从cookie中获取验证码ID
|
||||
cookie, err := r.Cookie("captcha_id")
|
||||
if err != nil {
|
||||
|
||||
@@ -3,6 +3,7 @@ package admin
|
||||
import (
|
||||
"net/http"
|
||||
"networkDev/database"
|
||||
"networkDev/models"
|
||||
"networkDev/services"
|
||||
"networkDev/utils"
|
||||
"networkDev/utils/timeutil"
|
||||
@@ -10,7 +11,24 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// AdminIndexHandler /admin 与 /admin/ 根路径入口
|
||||
// formatDBType 格式化数据库类型显示
|
||||
// 将配置文件中的小写类型转换为友好的显示格式
|
||||
func formatDBType(dbType string) string {
|
||||
switch dbType {
|
||||
case "mysql":
|
||||
return "MySQL"
|
||||
case "sqlite":
|
||||
return "SQLite"
|
||||
case "postgresql", "postgres":
|
||||
return "PostgreSQL"
|
||||
case "sqlserver":
|
||||
return "SQL Server"
|
||||
default:
|
||||
return "SQLite" // 默认显示
|
||||
}
|
||||
}
|
||||
|
||||
// AdminIndexHandler 后台首页处理器/admin 与 /admin/ 根路径入口
|
||||
// - 未登录:重定向到 /admin/login
|
||||
// - 已登录:渲染后台布局页(或重定向到 /admin/layout)
|
||||
// - 自动清理失效的JWT Cookie
|
||||
@@ -71,10 +89,10 @@ func AdminLayoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// DashboardFragmentHandler 仪表盘片段渲染
|
||||
// - 展示系统信息:版本、运行模式、数据库类型、启动时长
|
||||
// - 展示系统信息:版本、开发模式、数据库类型、启动时长
|
||||
func DashboardFragmentHandler(w http.ResponseWriter, r *http.Request) {
|
||||
version := "1.0.0"
|
||||
mode := viper.GetString("server.mode")
|
||||
mode := viper.GetBool("server.dev_mode")
|
||||
dbType := viper.GetString("database.type")
|
||||
if dbType == "" {
|
||||
dbType = "sqlite"
|
||||
@@ -84,7 +102,7 @@ func DashboardFragmentHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{
|
||||
"Version": version,
|
||||
"Mode": mode,
|
||||
"DBType": dbType,
|
||||
"DBType": formatDBType(dbType),
|
||||
"Uptime": uptime,
|
||||
}
|
||||
|
||||
@@ -100,7 +118,7 @@ func SystemInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
version := "1.0.0"
|
||||
mode := viper.GetString("server.mode")
|
||||
mode := viper.GetBool("server.dev_mode")
|
||||
dbType := viper.GetString("database.type")
|
||||
if dbType == "" {
|
||||
dbType = "sqlite"
|
||||
@@ -110,9 +128,64 @@ func SystemInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{
|
||||
"version": version,
|
||||
"mode": mode,
|
||||
"db_type": dbType,
|
||||
"db_type": formatDBType(dbType),
|
||||
"uptime": uptime,
|
||||
}
|
||||
|
||||
utils.JsonResponse(w, http.StatusOK, true, "ok", data)
|
||||
}
|
||||
|
||||
// DashboardStatsHandler 仪表盘统计数据API接口
|
||||
// - 返回应用统计数据的JSON数据,包括全部/启用/禁用/变量数量
|
||||
func DashboardStatsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取数据库连接
|
||||
db, err := database.GetDB()
|
||||
if err != nil {
|
||||
utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 统计应用数据
|
||||
var totalApps int64
|
||||
var enabledApps int64
|
||||
var disabledApps int64
|
||||
var totalVariables int64
|
||||
|
||||
// 统计全部应用数量
|
||||
if err := db.Model(&models.App{}).Count(&totalApps).Error; err != nil {
|
||||
utils.JsonResponse(w, http.StatusInternalServerError, false, "统计应用数量失败", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 统计启用应用数量
|
||||
if err := db.Model(&models.App{}).Where("status = ?", 1).Count(&enabledApps).Error; err != nil {
|
||||
utils.JsonResponse(w, http.StatusInternalServerError, false, "统计启用应用数量失败", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 统计禁用应用数量
|
||||
if err := db.Model(&models.App{}).Where("status = ?", 0).Count(&disabledApps).Error; err != nil {
|
||||
utils.JsonResponse(w, http.StatusInternalServerError, false, "统计禁用应用数量失败", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 统计变量数量
|
||||
if err := db.Model(&models.Variable{}).Count(&totalVariables).Error; err != nil {
|
||||
utils.JsonResponse(w, http.StatusInternalServerError, false, "统计变量数量失败", nil)
|
||||
return
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"total_apps": totalApps,
|
||||
"enabled_apps": enabledApps,
|
||||
"disabled_apps": disabledApps,
|
||||
"total_variables": totalVariables,
|
||||
}
|
||||
|
||||
utils.JsonResponse(w, http.StatusOK, true, "ok", data)
|
||||
}
|
||||
|
||||
@@ -81,11 +81,7 @@ func UserPasswordUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// 确认是管理员
|
||||
if !claims.IsAdmin {
|
||||
utils.JsonResponse(w, http.StatusForbidden, false, "权限不足", nil)
|
||||
return
|
||||
}
|
||||
// 注释:由于使用了AdminAuthRequired中间件,已确保是管理员用户
|
||||
|
||||
// 获取数据库连接
|
||||
db, err := database.GetDB()
|
||||
@@ -176,7 +172,7 @@ func UserProfileUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
claims, _, err := GetCurrentAdminUserWithRefresh(w, r)
|
||||
_, _, err := GetCurrentAdminUserWithRefresh(w, r)
|
||||
if err != nil {
|
||||
utils.JsonResponse(w, http.StatusUnauthorized, false, "未登录或会话已过期", nil)
|
||||
return
|
||||
@@ -207,11 +203,7 @@ func UserProfileUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// 确认当前用户是管理员
|
||||
if !claims.IsAdmin {
|
||||
utils.JsonResponse(w, http.StatusForbidden, false, "权限不足", nil)
|
||||
return
|
||||
}
|
||||
// 注释:由于使用了AdminAuthRequired中间件,已确保是管理员用户
|
||||
|
||||
// 获取所有管理员相关设置
|
||||
var adminSettings []models.Settings
|
||||
|
||||
Reference in New Issue
Block a user