Files
NetworkAuth/controllers/admin/captcha.go

168 lines
4.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package admin
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"math/big"
"net/http"
"strings"
"networkDev/utils"
"github.com/mojocn/base64Captcha"
"github.com/spf13/viper"
)
// 全局验证码存储器
var store = base64Captcha.DefaultMemStore
// 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 - 返回验证码图片
func CaptchaHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
// 随机生成4-6位长度
// 使用crypto/rand生成安全的随机数
randomNum, err := secureRandomInt(3)
if err != nil {
http.Error(w, "生成随机数失败", http.StatusInternalServerError)
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 {
http.Error(w, "生成验证码失败", http.StatusInternalServerError)
return
}
// 将验证码ID存储到session中这里简化处理实际项目中应该使用更安全的方式
// 设置cookie来存储验证码ID
cookie := utils.CreateSecureCookie("captcha_id", id, 300) // 5分钟过期
http.SetCookie(w, cookie)
// 解码base64图片数据并返回
w.Header().Set("Content-Type", "image/png")
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("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 {
http.Error(w, "解码验证码图片失败", http.StatusInternalServerError)
return
}
w.Write(imgData)
}
// VerifyCaptcha 验证验证码
// 这个函数将在登录处理中被调用
// 支持大小写不敏感匹配
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 {
return false
}
captchaId := cookie.Value
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 - 验证验证码
func CaptchaAPIHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
var body struct {
Captcha string `json:"captcha"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"code": 1,
"msg": "请求参数错误",
})
return
}
isValid := VerifyCaptcha(r, body.Captcha)
w.Header().Set("Content-Type", "application/json")
if isValid {
json.NewEncoder(w).Encode(map[string]interface{}{
"code": 0,
"msg": "验证码正确",
})
} else {
json.NewEncoder(w).Encode(map[string]interface{}{
"code": 1,
"msg": "验证码错误",
})
}
}