0


golang gin base64Captcha生成验证码后通过redis存储 以及安全验证思路

在学习过程中,我遇到了一个场景:用户使用邮箱进行登录注册,此时需要向对应邮箱发送验证码。之后用户输入验证码后,我们需要对验证码进行校验。这里我选择使用第三方包base64Captcha生成验证码,并且使用redis作为验证码池进行存储,代码如下:

1.创建一个redis验证码池,确保你已经配置了redis环境,这里必须实现以下base64Captcha中的Store接口

  1. type Store interface {
  2. // Set sets the digits for the captcha id.
  3. //验证码存入验证码池的方法,id为键,验证码为值
  4. Set(id string, value string) error
  5. // Get returns stored digits for the captcha id. Clear indicates
  6. // whether the captcha must be deleted from the store.
  7. //获取验证码的方法
  8. Get(id string, clear bool) string
  9. //Verify captcha's answer directly
  10. //校验验证码的方法
  11. Verify(id, answer string, clear bool) bool
  12. }

对这个接口的实现方法如下

  1. //自定义一个验证码池
  2. //以redis作为验证码池
  3. var ctx = context.Background()
  4. const CAPTCHA = "captcha:"
  5. type RedisStore struct {
  6. }
  7. // Set sets the digits for the captcha id.
  8. func (r RedisStore) Set(id string, value string) error {
  9. key := CAPTCHA + id
  10. err := global.RedisDb.Set(ctx, key, value, time.Minute*2).Err()
  11. if err != nil {
  12. log.Println(err.Error())
  13. }
  14. return err
  15. }
  16. // Get returns stored digits for the captcha id. Clear indicates
  17. // whether the captcha must be deleted from the store.
  18. func (r RedisStore) Get(id string, clear bool) string {
  19. key := CAPTCHA + id
  20. val, err := global.RedisDb.Get(ctx, key).Result()
  21. if err != nil {
  22. fmt.Println(err)
  23. return ""
  24. }
  25. if clear {
  26. err := global.RedisDb.Del(ctx, key).Err()
  27. if err != nil {
  28. fmt.Println(err)
  29. return ""
  30. }
  31. }
  32. return val
  33. }
  34. // Verify captcha's answer directly
  35. func (r RedisStore) Verify(id, answer string, clear bool) bool {
  36. v := RedisStore{}.Get(id, clear)
  37. //fmt.Println("key:"+id+";value:"+v+";answer:"+answer)
  38. return v == answer
  39. }

2.创建一个用于发送验证码到邮箱的utils工具(具体的邮箱配置忽略)

  1. package utils
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/jordan-wright/email"
  6. "github.com/mojocn/base64Captcha"
  7. "net/smtp"
  8. )
  9. // 发送邮箱验证码
  10. func SendEmailValidate(em []string, myType int) (string, error) {
  11. e := email.NewEmail()
  12. e.From = fmt.Sprintf("发件人邮箱")
  13. e.To = em
  14. // 生成6位随机验证码
  15. driver := base64Captcha.NewDriverDigit(80, 240, 5, 0.7, 80)
  16. //这里在生成验证码对象时,就使用了redis作为验证码池
  17. cp := base64Captcha.NewCaptcha(driver, cache.RedisStore{})
  18. id, _, anwser, err := cp.Generate()
  19. fmt.Println("id", id)
  20. fmt.Println("anwser", anwser)
  21. if err != nil {
  22. fmt.Println("err")
  23. return "", err
  24. }
  25. ems := em[0]
  26. //为id添加验证码类型,1为注册,2为登录
  27. if myType == 1 {
  28. id = fmt.Sprintf("%s&amp%s", id, "emailtoregister")
  29. } else if myType == 2 {
  30. id = fmt.Sprintf("%s&amp%s", id, "emailtologin")
  31. } else {
  32. id = fmt.Sprintf("%s&amp%s", id, "emailtoforgetpassword")
  33. }
  34. cmd := global.RedisDb.Set(context.Background(), ems, id, 2*time.Minute)
  35. if cmd.Err() != nil {
  36. log.Fatal(cmd.Err())
  37. return "", cmd.Err()
  38. }
  39. //设置文件发送的内容
  40. content := fmt.Sprintf(`
  41. <div>
  42. <div>
  43. 您好!
  44. </div>
  45. <div style="padding: 8px 40px 8px 50px;">
  46. <p>您提交的邮箱验证,本次验证码为<u><strong>%s</strong></u>,为了保证账号安全,验证码有效期为5分钟。请确认为本人操作,切勿向他人泄露,感谢您的理解与使用。</p>
  47. </div>
  48. <div>
  49. <p>此邮箱为系统邮箱,请勿回复。</p>
  50. </div>
  51. </div>
  52. `, anwser)
  53. e.Text = []byte(content)
  54. //设置服务器相关的配置
  55. err = e.Send("smtp.qq.com:25", smtp.PlainAuth("", "发件人邮箱", "发件人的密钥", "smtp.qq.com"))
  56. fmt.Println("to", e.To)
  57. return anwser, err
  58. }

3.gin框架发送验证码的路由

  1. // 发送注册验证码
  2. func GetValidateCode(c *gin.Context) {
  3. // 获取目的邮箱
  4. ems := c.Param("email")
  5. temp := strings.Split(ems, "@")
  6. if len(temp) < 2 {
  7. c.JSON(http.StatusBadRequest, gin.H{
  8. "msg": "请输入正确邮箱",
  9. "code": 400,
  10. "data": nil,
  11. })
  12. return
  13. }
  14. em := []string{ems}
  15. fmt.Println(ems)
  16. fmt.Println(em)
  17. //发送验证码,并将验证码id和email存入redis
  18. _, err := utils.SendEmailValidate(em, 1)
  19. if err != nil {
  20. log.Println(err)
  21. c.JSON(http.StatusBadRequest, gin.H{
  22. "status": 400,
  23. "msg": "验证码发送失败",
  24. "ERROR-CONTROLLER": err.Error(),
  25. })
  26. return
  27. }
  28. c.JSON(http.StatusOK, gin.H{
  29. "msg": "验证码发送成功",
  30. "status": 200,
  31. })
  32. return
  33. }

代码部分到此结束,接下来我要讲解以下验证码的校验思路,来看两个问题:

  1. 1.假定,现在恶意攻击者准备用别人的邮箱以爆破的方式登入别人的账号时,如果我们只是简单的对验证码做存储和校验会遇到什么问题呢?
  2. 1)验证码池冗余:可以预见的一个问题是,如果攻击者不断向我们的后端发起请求验证码的请求,即便我们设置了过期时间,但是在大量请求的情况下验证码池还是会冗余大量的验证码。而在我们底层,只有base64Captcha自动生成的id和验证码做绑定。即便id的生成是随机的,但这也无疑增加了爆破成功的概率。
  3. 为了解决这个问题,我们可以在id为键,存贮验证码的基础上再加一层,即邮箱为键,绑定id。这样一来,无论请求方如何请求我们的验证码,因为邮箱是不变的,且对于id来说是唯一的的,所以每次请求都会把上一次存储的id覆盖,也就意味着上一次请求的验证码是无效的。
  4. 2)验证码越权:第二个问题,在上述问题中,我们使用了邮箱和id进行绑定。但是依旧存在问题,假设现在,一个用户在邮箱注册处获得了一个验证码,结果他用这个验证码去进行登录。这在业务上极其不合理,但是按照底层逻辑来说这是能实现的。因为邮箱是不变的,无论登录和注册,我获取验证码都是通过这一个邮箱号。
  5. 为了解决这个问题,我选择的方法是:在原生的id后拼接标识符,以达到标识这个验证码对应的是哪一个接口的目的。也即id + 分隔符 + 标识符。这样一来,我只要通过分隔符切分,取到数组的最后一个,再在对应路由中,判断这个验证码是否为对应功能即可。具体验证方法如下:
  1. // ValidateEmailCode
  2. // @Title ValidateEmailCode
  3. // @Description 验证邮箱验证码,并注册用户。
  4. // @Author hyy 2022-03-05 18:19:18
  5. // @Param c type description
  6. func ValidateEmailCode(c *gin.Context) {
  7. //检验邮箱是否合法
  8. var user forms.CreateUserByEmail
  9. err := c.ShouldBindJSON(&user)
  10. fmt.Println(user)
  11. if err != nil {
  12. log.Println(err.Error())
  13. c.JSON(http.StatusBadRequest, gin.H{
  14. "status": 400,
  15. "msg": "注册失败,json解析失败",
  16. "ERROR-CONTROLLER": err.Error(),
  17. })
  18. return
  19. }
  20. myEmail := user.Email
  21. // 默认用户权限为2
  22. if user.RoleId == 0 {
  23. user.RoleId = 3
  24. }
  25. // 通过邮箱获取在redis中绑定的id
  26. id, err := global.RedisDb.Get(context.Background(), myEmail).Result()
  27. //分隔符切分
  28. myResp := strings.Split(id, "&amp")
  29. if len(myResp) < 2 {
  30. c.JSON(http.StatusInternalServerError, gin.H{
  31. "msg": "验证码失效",
  32. "code": 500,
  33. "data": nil,
  34. })
  35. return
  36. }
  37. //判断接口是否对应
  38. if myResp[1] != "emailtoregister" {
  39. c.JSON(http.StatusBadRequest, gin.H{
  40. "msg": "验证码失效",
  41. "data": nil,
  42. "code": 400,
  43. })
  44. return
  45. }
  46. id = myResp[0]
  47. if err != nil {
  48. log.Println(err.Error())
  49. c.JSON(http.StatusBadRequest, gin.H{
  50. "status": 400,
  51. "msg": "Redis获取vCode失败",
  52. "ERROR-CONTROLLER": err.Error(),
  53. })
  54. return
  55. }
标签: 安全

本文转载自: https://blog.csdn.net/qq_62906402/article/details/139880144
版权归原作者 桐生战兔868 所有, 如有侵权,请联系我们删除。

“golang gin base64Captcha生成验证码后通过redis存储 以及安全验证思路”的评论:

还没有评论