Files
NetworkAuth/controllers/admin/captcha.go

151 lines
4.1 KiB
Go
Raw Normal View History

package admin
import (
2025-10-24 08:25:16 +08:00
"crypto/rand"
"encoding/base64"
2025-10-24 08:25:16 +08:00
"math/big"
"net/http"
"strings"
2025-10-26 14:48:02 +08:00
"github.com/gin-gonic/gin"
"networkDev/controllers"
"networkDev/middleware"
"networkDev/utils"
"github.com/mojocn/base64Captcha"
)
2025-10-26 14:48:02 +08:00
// 创建基础控制器实例
var captchaBaseController = controllers.NewBaseController()
// 全局验证码存储器
var store = base64Captcha.DefaultMemStore
2025-10-24 08:25:16 +08:00
// secureRandomInt 生成安全的随机整数,范围 [0, max)
func secureRandomInt(max int) (int, error) {
n, err := rand.Int(rand.Reader, big.NewInt(int64(max)))
if err != nil {
return 0, err
}
return int(n.Int64()), nil
}
// CaptchaHandler 生成验证码图片
// GET /admin/captcha - 返回验证码图片
2025-10-26 14:48:02 +08:00
func CaptchaHandler(c *gin.Context) {
// 随机生成4-6位长度
2025-10-24 08:25:16 +08:00
// 使用crypto/rand生成安全的随机数
randomNum, err := secureRandomInt(3)
if err != nil {
2025-10-26 14:48:02 +08:00
captchaBaseController.HandleInternalError(c, "生成随机数失败", err)
2025-10-24 08:25:16 +08:00
return
}
captchaLength := 4 + randomNum // 4-6位随机长度
// 配置验证码参数 - 使用字母数字混合
driver := base64Captcha.DriverString{
Height: 60,
Width: 200,
NoiseCount: 0,
ShowLineOptions: 2 | 4,
Length: captchaLength,
Source: "ABCDEFGHJKMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789", // 混合大小写字母和数字,去除易混淆字符
Fonts: []string{"wqy-microhei.ttc"},
}
// 生成验证码
captcha := base64Captcha.NewCaptcha(&driver, store)
id, b64s, _, err := captcha.Generate()
if err != nil {
2025-10-26 14:48:02 +08:00
captchaBaseController.HandleInternalError(c, "生成验证码失败", err)
return
}
// 将验证码ID存储到session中这里简化处理实际项目中应该使用更安全的方式
// 设置cookie来存储验证码ID
cookie := utils.CreateSecureCookie("captcha_id", id, 300) // 5分钟过期
2025-10-26 14:48:02 +08:00
c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly)
// 解码base64图片数据并返回
2025-10-26 14:48:02 +08:00
c.Header("Content-Type", "image/png")
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
c.Header("Pragma", "no-cache")
c.Header("Expires", "0")
// 直接返回base64编码的图片数据让浏览器解析
// 但是我们需要返回实际的图片数据所以需要解码base64
// 去掉data:image/png;base64,前缀
b64s = strings.TrimPrefix(b64s, "data:image/png;base64,")
imgData, err := base64.StdEncoding.DecodeString(b64s)
if err != nil {
2025-10-26 14:48:02 +08:00
captchaBaseController.HandleInternalError(c, "解码验证码图片失败", err)
return
}
2025-10-26 14:48:02 +08:00
c.Data(http.StatusOK, "image/png", imgData)
}
2025-10-26 14:48:02 +08:00
// VerifyCaptcha 验证验证码
// 这个函数将在登录处理中被调用
// 支持大小写不敏感匹配
2025-10-26 14:48:02 +08:00
func VerifyCaptcha(c *gin.Context, captchaValue string) bool {
2025-10-26 11:57:31 +08:00
// 检查是否为开发模式,如果是则跳过验证码验证
if middleware.ShouldSkipCaptcha(c) {
2025-10-26 11:57:31 +08:00
return true
}
// 从cookie中获取验证码ID
2025-10-26 14:48:02 +08:00
captchaId, err := c.Cookie("captcha_id")
if err != nil {
return false
}
if captchaId == "" {
return false
}
// 先尝试原始值验证
if store.Verify(captchaId, captchaValue, false) {
// 验证成功后删除验证码
store.Verify(captchaId, captchaValue, true)
return true
}
// 如果原始值验证失败,尝试小写验证(因为显示的是大小写混合,但允许用户输入小写)
if store.Verify(captchaId, strings.ToLower(captchaValue), false) {
// 验证成功后删除验证码
store.Verify(captchaId, strings.ToLower(captchaValue), true)
return true
}
// 最后尝试大写验证
if store.Verify(captchaId, strings.ToUpper(captchaValue), true) {
return true
}
return false
}
// CaptchaAPIHandler 验证码API接口可选用于AJAX验证
// POST /admin/api/captcha/verify - 验证验证码
2025-10-26 14:48:02 +08:00
func CaptchaAPIHandler(c *gin.Context) {
var body struct {
Captcha string `json:"captcha"`
}
2025-10-26 14:48:02 +08:00
if !captchaBaseController.BindJSON(c, &body) {
return
}
2025-10-26 14:48:02 +08:00
isValid := VerifyCaptcha(c, body.Captcha)
if isValid {
2025-10-26 14:48:02 +08:00
captchaBaseController.HandleSuccess(c, "验证码正确", nil)
} else {
2025-10-26 14:48:02 +08:00
captchaBaseController.HandleValidationError(c, "验证码错误")
}
}