Skip to the content.

这是登录的后台逻辑请参考

package middleware

import (
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"gateway/setting"
	"net/http"
	"time"

	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
)

// 定义一个jwt对象
type JWT struct {
	// 声明签名信息
	SigningKey []byte
}

// 自定义有效载荷(这里采用自定义的Name和Email作为有效载荷的一部分)
type CustomClaims struct {
	Name  string `json:"name"`
	Email string `json:"email"`
	// StandardClaims结构体实现了Claims接口(Valid()函数)
	jwt.StandardClaims
}

// 构造用户表
type User struct {
	Id        int32  `gorm:"AUTO_INCREMENT"`
	Name      string `json:"username"`
	Pwd       string `json:"password"`
	Phone     int64  `gorm:"DEFAULT:0"`
	Email     string `gorm:"type:varchar(20);unique_index;"`
	CreatedAt *time.Time
	UpdateTAt *time.Time
}

// LoginReq请求参数
type LoginReq struct {
	Name  string `json:"username"`
	Pwd   string `json:"password"` // 前端传来的 SHA256(password + nonce)
	Nonce string `json:"nonce"`    // 前端传来的一次性随机串
}

// 登陆结果
type LoginResultTemplate struct {
	Token       string                        `json:"token"`
	Name        string                        `json:"username"`
	Permissions []setting.PermissionsTemplate `json:"permissions"`
}

// ==========================
// 登录爆破防护逻辑(新增)
// ==========================

// 允许最大失败次数
const MaxLoginFailCount = 10

// 达到最大失败次数后的封锁时长
const LoginBlockDuration = 3 * time.Minute

// 记录登录失败次数
type LoginFailInfo struct {
	Count      int
	LastFailAt time.Time
	BlockUntil time.Time
}

var loginFailMap = make(map[string]*LoginFailInfo)

var (
	TokenExpired error = errors.New("Token is expired")

	LoginResult LoginResultTemplate
)

// 初始化jwt对象
func NewJWT() *JWT {
	return &JWT{
		[]byte("GATEWAY"),
	}
}

// 调用jwt-go库生成token
// 指定编码的算法为jwt.SigningMethodHS256
func (j *JWT) CreateToken(claims CustomClaims) (string, error) {
	// https://gowalker.org/github.com/dgrijalva/jwt-go#Token
	// 返回一个token的结构体指针
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(j.SigningKey)
}

// token解码
func (j *JWT) ParserToken(tokenString string) (*CustomClaims, error) {
	// https://gowalker.org/github.com/dgrijalva/jwt-go#ParseWithClaims
	// 输入用户自定义的Claims结构体对象,token,以及自定义函数来解析token字符串为jwt的Token结构体指针
	// Keyfunc是匿名函数类型: type Keyfunc func(*Token) (interface{}, error)
	// func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {}
	token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		return j.SigningKey, nil
	})

	if err != nil {
		// https://gowalker.org/github.com/dgrijalva/jwt-go#ValidationError
		// jwt.ValidationError 是一个无效token的错误结构
		if ve, ok := err.(*jwt.ValidationError); ok {
			// ValidationErrorMalformed是一个uint常量,表示token不可用
			if ve.Errors&jwt.ValidationErrorMalformed != 0 {
				return nil, fmt.Errorf("token不可用")
				// ValidationErrorExpired表示Token过期
			} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
				return nil, fmt.Errorf("token过期")
				// ValidationErrorNotValidYet表示无效token
			} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
				return nil, fmt.Errorf("无效的token")
			} else {
				return nil, fmt.Errorf("token不可用")
			}

		}
	}

	// 将token中的claims信息解析出来并断言成用户自定义的有效载荷结构
	if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
		return claims, nil
	}

	return nil, fmt.Errorf("token无效")

}

// 定义一个JWTAuth的中间件
func JWTAuth() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 通过http header中的token解析来认证
		token := c.Request.Header.Get("token")
		if token == "" {
			c.JSON(http.StatusUnauthorized, gin.H{
				"code":    "1",
				"message": "请求未携带token,无权限访问",
				"data":    "",
			})
			c.Abort()
			return
		}
		// 初始化一个JWT对象实例,并根据结构体方法来解析token
		j := NewJWT()
		// 解析token中包含的相关信息(有效载荷)
		claims, err := j.ParserToken(token)
		if err != nil {
			// token过期
			if err.Error() == "token不可用" {
				c.JSON(http.StatusUnauthorized, gin.H{
					"code":    "1",
					"message": "token不可用",
					"data":    "",
				})
				c.Abort()
				return
			} else if err.Error() == "token过期" {
				c.JSON(http.StatusUnauthorized, gin.H{
					"code":    "1",
					"message": "登录已经过期,请重新登录",
					"data":    "",
				})
				c.Abort()
				return
			}

			setting.ZAPS.Errorf("gin解析token错误 %v", err)

			// 其他错误
			c.JSON(http.StatusBadRequest, gin.H{
				"code":    "1",
				"message": fmt.Sprintf("gin解析token错误 %v", err.Error()),
				"data":    "",
			})
			c.Abort()
			return
		}

		// 将解析后的有效载荷claims重新写入gin.Context引用对象中
		c.Set("claims", claims)
	}
}

// LoginCheck验证
// LoginCheck验证
func LoginCheck(login LoginReq) (bool, User, error) {
	userData := User{}
	userExist := false
	if !ValidateAndConsumeNonce(login.Nonce) {
		return false, userData, fmt.Errorf("加密已过期,请再次登录")
	}
	// 遍历配置用户
	for _, v := range setting.PolicyWeb {
		if v.Role == login.Name {
			userExist = true
			// 计算数据库密码 + nonce 的 SHA256
			hashBytes := sha256.Sum256([]byte(v.Password + login.Nonce))
			serverHash := hex.EncodeToString(hashBytes[:])

			if serverHash != login.Pwd {
				return false, userData, fmt.Errorf("登陆信息有误")
			}

			userData.Name = v.Role
			userData.Email = "" // 可根据需要填写
			return true, userData, nil
		}
	}

	if !userExist {
		return false, userData, fmt.Errorf("用户不存在")
	}

	return false, userData, fmt.Errorf("未知错误")
}

// token生成器
// md 为上面定义好的middleware中间件
func GenerateToken(c *gin.Context, user User) {
	// 构造SignKey: 签名和解签名需要使用一个值
	j := NewJWT()

	// 构造用户claims信息(负荷)
	claims := CustomClaims{
		user.Name,
		user.Email,
		jwt.StandardClaims{
			NotBefore: time.Now().Unix(),           // 签名生效时间
			ExpiresAt: time.Now().Unix() + 3600*96, // 签名过期时间
			Issuer:    "HNE",                       // 签名颁发者
		},
	}

	// 根据claims生成token对象
	token, err := j.CreateToken(claims)

	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code":    "1",
			"message": err.Error(),
			"data":    "",
		})
		return
	}

	for _, v := range setting.PolicyWeb {
		if v.Role == claims.Name {
			LoginResult.Permissions = v.Policy
		}
	}

	data := LoginResultTemplate{
		Name:        user.Name,
		Token:       token,
		Permissions: LoginResult.Permissions,
	}
	LoginResult = data
	c.JSON(http.StatusOK, gin.H{
		"code":    "0",
		"message": "登录成功",
		"data":    data,
	})
	return

}

// 获取客户端真实 IP
func GetClientIP(c *gin.Context) string {
	ip := c.ClientIP()
	if ip == "" {
		ip = c.RemoteIP()
	}
	if ip == "" {
		ip = "unknown"
	}
	return ip
}

// 检查当前 IP 是否被封锁
func IsIPBlocked(ip string) (bool, time.Duration) {
	info, exists := loginFailMap[ip]
	if !exists {
		return false, 0
	}

	if time.Now().Before(info.BlockUntil) {
		return true, time.Until(info.BlockUntil)
	}

	// 自动解除封锁
	return false, 0
}

// 记录失败
func AddLoginFail(ip string) {
	info, exists := loginFailMap[ip]
	if !exists {
		loginFailMap[ip] = &LoginFailInfo{
			Count:      1,
			LastFailAt: time.Now(),
			BlockUntil: time.Time{},
		}
		return
	}

	info.Count++
	info.LastFailAt = time.Now()

	if info.Count >= MaxLoginFailCount {
		info.BlockUntil = time.Now().Add(LoginBlockDuration)
	}
}

// 登录成功后清除
func ClearLoginFail(ip string) {
	delete(loginFailMap, ip)
}

随机数的逻辑

package middleware

import (
	"crypto/rand"
	"encoding/hex"
	"sync"
	"time"

	"golang.org/x/time/rate"
)

// =====================
//
//	nonce 全局存储
//
// =====================
var (
	nonceStore sync.Map
	nonceMax   = 100000 // 最大允许缓存的 nonce 数量,避免内存膨胀
)

// =====================
//
//	IP 限流器(登录、防刷 nonce)
//
// =====================
var nonceLimiters sync.Map

func GetLimiter(ip string) *rate.Limiter {
	if l, ok := nonceLimiters.Load(ip); ok {
		return l.(*rate.Limiter)
	}
	limiter := rate.NewLimiter(2, 5) // 每秒 5 次、突发 10 次
	nonceLimiters.Store(ip, limiter)
	return limiter
}

// =====================
//
//	生成 nonce(一次性随机值,有效期 2 分钟)
//
// =====================
func GenerateNonce() (string, error) {
	// 防止 DoS:nonceStore 超过上限直接拒绝生成
	size := 0
	nonceStore.Range(func(_, _ any) bool {
		size++
		return true
	})
	if size > nonceMax {
		return "", nil
	}

	b := make([]byte, 16)
	_, err := rand.Read(b)
	if err != nil {
		return "", err
	}

	nonce := hex.EncodeToString(b)
	nonceStore.Store(nonce, time.Now().Add(2*time.Minute)) // 保存过期时间

	return nonce, nil
}

// =====================
//
//	验证并消耗 nonce
//
// =====================
func ValidateAndConsumeNonce(nonce string) bool {
	v, ok := nonceStore.Load(nonce)
	if !ok {
		return false
	}

	expire := v.(time.Time)
	if time.Now().After(expire) {
		nonceStore.Delete(nonce)
		return false
	}

	// 验证通过后立即删除,防重放攻击
	nonceStore.Delete(nonce)
	return true
}

// =====================
//
//	启动后台 GC 清理过期 nonce
//
// =====================
func init() {
	go func() {
		ticker := time.NewTicker(1 * time.Minute)
		for range ticker.C {
			nonceStore.Range(func(key, value any) bool {
				expire := value.(time.Time)
				if time.Now().After(expire) {
					nonceStore.Delete(key)
				}
				return true
			})
		}
	}()
}