go-图片、手机邮箱验证码(一文齐全)

532 阅读6分钟

1. 前言

图片验证码和手机邮箱验证码都是安全性工具,用于验证用户身份和防止恶意活动。它们在不同的情境中有着类似的作用:

  1. 防止机器人和自动化攻击: 图片验证码和手机邮箱验证码是防止机器人和自动化脚本攻击的有效手段。通过要求用户输入验证码,系统可以确保操作是由真实的用户执行,而不是由程序或脚本自动完成的。
  2. 保护用户隐私: 手机邮箱验证码通常会发送到用户注册时提供的手机号码或电子邮箱地址,以确保只有注册用户才能完成特定的操作。这有助于保护用户的隐私,防止未经授权的访问或操作。
  3. 降低暴力攻击风险: 恶意攻击者可能尝试通过大量的尝试(暴力攻击)来破解密码或执行其他恶意操作。验证码可以限制尝试的频率,从而有效地减少这种类型的攻击。
  4. 提高安全性: 验证码是多因素身份验证的一部分,提高了系统的安全性。即使攻击者知道用户的密码,他们仍然需要验证码才能完成登录或其他关键操作。
  5. 防范钓鱼攻击: 通过将验证码与特定的操作相关联,系统可以减少用户受到钓鱼攻击的风险。用户收到的验证码通常只在特定的上下文中有效,使得攻击者很难将验证码用于欺诈性的目的。

总的来说,在一些较敏感或易受攻击的请求中,图片验证码是非常常见的验证方式这里只简单介绍文字验证码,滑动验证码、图像切割验证码等不再过多赘述。而手机邮箱验证码常用于登录、密码找回等场景,下面均会简单使用。

2. base64Captcha图片验证码

2.1 获取依赖

go get github.com/mojocn/base64Captcha
go get -u github.com/liuhongdi/digv18/pkg/result

2.2 封装工具类

package tools

import (
	"fmt"
	"github.com/mojocn/base64Captcha"
	"image/color"
)

// 设置自带的store
var store = base64Captcha.DefaultMemStore

// 生成验证码
func CaptMake() (id, b64s string, err error) {
	var driver base64Captcha.Driver
	var driverString base64Captcha.DriverString

	// 配置验证码信息
	captchaConfig := base64Captcha.DriverString{
		Height:          60,
		Width:           200,
		NoiseCount:      0,
		ShowLineOptions: 2 | 4,
		Length:          4,
		Source:          "1234567890qwertyuioplkjhgfdsazxcvbnm",
		BgColor: &color.RGBA{
			R: 3,
			G: 102,
			B: 214,
			A: 125,
		},
		Fonts: []string{"wqy-microhei.ttc"},
	}

	driverString = captchaConfig
	driver = driverString.ConvertFonts()
	captcha := base64Captcha.NewCaptcha(driver, store)
	lid, lb64s, lerr := captcha.Generate()
	return lid, lb64s, lerr

}

// 验证captcha是否正确
func CaptVerify(id string, capt string) bool {
	if store.Verify(id, capt, false) {
		return true
	} else {
		return false
	}
}

2.3 具体逻辑

package logic

import (
	"book/tools"
	"github.com/gin-gonic/gin"
)

type CaptchaController struct{}

func NewCaptchaController() *CaptchaController {
	return &CaptchaController{}
}

func (c *CaptchaController) GetCaptcha(ctx *gin.Context) {
	// 生成验证码
	id, b64s, err := tools.CaptMake()
	if err != nil {
		ctx.JSON(500, err)
		return
	}
	ctx.JSON(200, gin.H{"id": id, "captcha": b64s})
}

func (c *CaptchaController) VerifyCaptcha(ctx *gin.Context) {
	// 验证验证码
	id := ctx.PostForm("id")
	code := ctx.PostForm("code")
	if tools.CaptVerify(id, code) {
		ctx.JSON(200, "verification passed")
	} else {
		ctx.JSON(400, "incorrect captcha")
	}
}

详细不再过多解释,具体可看下面这篇文章: 图片验证码go语言 - 掘金 (juejin.cn)

3. 邮箱验证码

3.1 配置文件

smtp:
    email: xxx@qq.com
    host: smtp.qq.com
    password: qsxujiaarqpvbhcf(SMTP服务密码)
    port: 587

我使用的是go-viper进行的配置文件读取,有兴趣可以网上搜教程学习一下,只测试邮箱验证码,直接将以上信息填入下方工具类中即可。

SMTP服务网上教程很多,我这里只简单介绍QQ邮箱示例: 进入QQ邮箱-->设置-->服务-->POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务

3.2 工具类

package tools

import (
	"book/appV2/model"
	"fmt"
	"math/rand"
	"net/mail"
	"net/smtp"
	"strconv"
	"time"

	"github.com/spf13/viper"
)

type EmailUtil struct {
	SMTPHost string
	SMTPPort int
	Email    string
	Password string
}

func NewEmailUtil() *EmailUtil {
	return &EmailUtil{}
}
//获取配置信息,如不用配置文件,这里直接赋值即可
func (eu *EmailUtil) LoadConfig() {
	viper.SetConfigFile("./static/config.yaml")
	err := viper.ReadInConfig()
	if err != nil {
		fmt.Printf("Failed to read config file: %v", err)
	}
	eu.SMTPHost = viper.GetString("smtp.host")
	eu.SMTPPort = viper.GetInt("smtp.port")
	eu.Email = viper.GetString("smtp.email")
	eu.Password = viper.GetString("smtp.password")

}

func (eu *EmailUtil) SendVerificationCode(email string) (string, error) {
	code := GenerateVerificationCode()

	from := mail.Address{Name: "Notification", Address: eu.Email}

	// 2. 设置SMTP服务器相关信息
	auth := smtp.PlainAuth("", eu.Email, eu.Password, eu.SMTPHost)
	addr := fmt.Sprintf("%s:%d", eu.SMTPHost, eu.SMTPPort)

	// 3. 组装邮件内容
	header := make(map[string]string)
	header["From"] = from.String()
	header["To"] = email
	header["Subject"] = "Email verification code"
	header["MIME-Version"] = "1.0"
	header["Content-Type"] = "text/plain; charset=\"utf-8\""
	content := "你的验证码是:: " + code
	message := ""
	for k, v := range header {
		message += fmt.Sprintf("%s: %s\r\n", k, v)
	}
	message += "\r\n" + content
	// 4. 发送邮件
	err := smtp.SendMail(addr, auth, eu.Email, []string{email}, []byte(message))

	if err != nil {
		return "", err
	}

	// 存储验证码到 Redis
	key := generateVerificationCodeKey(email)
	err = model.SetRedis(key, code, time.Minute*5)
	if err != nil {
		return "", err
	}
	return key, nil
}
//验证
func (eu *EmailUtil) VerifyVerificationCode(email, key, code string) bool {
	// 1. 从Redis获取存储的验证码
	storedCode, err := model.GetRedis(key)
	if err != nil {
		return false
	}
	if email != key[18:] {
		return false
	}
	// 2. 比较验证码
	if storedCode != code {
		return false
	}
	// 3. 验证码匹配,可以删除Redis中的验证码
	_, err = model.DelRedis(key)
	if err != nil {
		fmt.Printf("Error deleting code: %v", err)
	}
	// 4. 返回成功
	return true
}
//生成6位数字验证码
func GenerateVerificationCode() string {
	rand.Seed(time.Now().UnixNano())
	return strconv.Itoa(rand.Intn(999999))
}
//设置redis-key
func generateVerificationCodeKey(email string) string {
	return fmt.Sprintf("verification_code:%s", email)
}

3.3 逻辑类

func SendEmailCode(c *gin.Context) {
	email := c.PostForm("email")
	emailUtil := tools.NewEmailUtil()
	emailUtil.LoadConfig()
	key, err := emailUtil.SendVerificationCode(email)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "发送验证码失败!"})
		return
	}
	c.JSON(http.StatusOK, gin.H{"key": key})
}

发送验证码有一定程度的延迟,可以起一个协程,这里不做修改。

4. 手机验证码

4.1 前期准备

        //示例
	accessKeyID     = "LTAI5tMzyasfsaebxEZGCuW"
	accessKeySecret = "HmRSCyJTUFn4asfasfUEML1X2CHT"
	signName        = "xxx"
	templateCode    = "SMS_462610985"

前往阿里云申请以上信息,地址:www.aliyun.com/

新用户会送你100条免费验证码,不需要充钱。

下面是大概位置,可以网上搜索教程,这里不过多详细解释。

image.png

4.2 安装依赖

go get -u github.com/aliyun/alibaba-cloud-sdk-go/sdk
go get -u github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests

4.3 工具类

package tools

import (
	"book/appV2/model"
	"fmt"
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
	"time"
)

const (
	accessKeyID     = "xxx"
	accessKeySecret = "xxx"
	signName        = "xxx"
	templateCode    = "xxx"
)

func SendSMS(phoneNumber, verificationCode string) (string, error) {
	client, err := sdk.NewClientWithAccessKey("your_region_id", accessKeyID, accessKeySecret)
	if err != nil {
		return "", err
	}

	request := requests.NewCommonRequest()
	request.Method = "POST"
	request.Domain = "dysmsapi.aliyuncs.com"
	request.Version = "2017-05-25"
	request.ApiName = "SendSms"

	request.QueryParams["RegionId"] = "cn-beijing" // 替换为你的区域ID
	request.QueryParams["PhoneNumbers"] = phoneNumber
	request.QueryParams["SignName"] = signName
	request.QueryParams["TemplateCode"] = templateCode
	request.QueryParams["TemplateParam"] = fmt.Sprintf(`{"code": "%s"}`, verificationCode)

	_, err = client.ProcessCommonRequest(request)
	if err != nil {
		return "", err
	}
	phoneKey := fmt.Sprintf("phoneCode:%s", phoneNumber)
	err = model.SetRedis(phoneKey, verificationCode, time.Minute*5)
	if err != nil {
		return "", err
	}

	return phoneKey, nil
}

func VerifyVerificationCode(phoneNumber, code, phoneKey string) bool {
	// 在这里进行验证码验证逻辑,比对 code 和存储的验证码是否一致
	// 1. 从Redis获取存储的验证码
	storedCode, err := model.GetRedis(phoneKey)
	if err != nil {
		return false
	}
	if phoneNumber != phoneKey[10:] {
		return false
	}
	// 2. 比较验证码
	if storedCode != code {
		return false
	}
	// 3. 验证码匹配,可以删除Redis中的验证码
	_, err = model.DelRedis(phoneKey)
	if err != nil {
		fmt.Printf("Error deleting code: %v", err)
	}
	// 4. 返回成功
	return true
}

逻辑

func PhoneCode(c *gin.Context) {

	phoneNumber := c.PostForm("phone")
	// 生成6位随机验证码
        // 方法看邮箱验证码
	verificationCode := tools.GenerateVerificationCode()

	// 发送验证码短信
	phoneKey, err := tools.SendSMS(phoneNumber, verificationCode)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{"message": "验证码已发送", "phoneKey": phoneKey})
}

4. 总结

如果对您有帮助,希望可以点个免费的赞,大家一起在敲代码的路上一起努力。0.0