# 网站实现验证码功能

# 一、验证码

一般来说,网站在登录的时候会生成一个验证码来验证是否是人类还是爬虫,还有一个好处是防止恶意人士对密码进行爆破。

# 二、流程图

# 三、详细说明

# 3.1 后端生成验证码

@Override
public Result<Map<String, String>> getVerificationCode() {
    // HuTool 定义图形验证码的长和宽, 验证码的位数,干扰线的条数
    // 线段干扰的验证码
    LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(116, 36, 4, 50);
    String uuid = UUID.randomUUID().toString();
    String verificationCodeKey = "VerificationCode:" + uuid;
    // 存到 redis 中
    ValueOperations<String, String> ops = redisTemplate.opsForValue();
    ops.set(verificationCodeKey, lineCaptcha.getCode());
    // 设置 15 分钟的过期时间
    redisTemplate.expire(verificationCodeKey, 15, TimeUnit.MINUTES);
    Map<String, String> map = new HashMap<>(2);
    map.put("verificationCodeBase64", lineCaptcha.getImageBase64());
    map.put("uuid", uuid);
    return Result.success(map);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

记得设置验证码过期时间,否则每一次生成验证码都会在 redis 里面产生数据,造成内存浪费。 返回给前端的 json

{
    "code": 200,
    "msg": "操作成功",
    "data": {
        "uuid": "bc814884-fffb-4943-834a-40cc92decfa6",
        "verificationCodeBase64": "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"
    }
}
1
2
3
4
5
6
7
8

Base64 是一种用于将二进制数据编码为 ASCII 字符的编码方法,我们这边用这个编码传递验证码图片,数据在 verificationCodeBase64

# 3.2 前端生成验证码图片

# 3.2.1 获取验证码 Base64 并且拼接

const updateVerificationCodeImage = async () => {
    // 图像类型
    const imageType = 'image/png';
    const res = await userGetVerificationCodeApi();
    if (res.data) {
      if (res.data.data) {
        verificationCodeBase64.value = res.data.data.verificationCodeBase64;
        uuid.value = res.data.data.uuid;
        // 拼接 Base64 图像数据
        verificationCodeImageSrc.value = `data:${imageType};base64,${verificationCodeBase64.value}`;
      }
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13

# 3.2.2 渲染到页面上

<div class="verificationCode-class">
  <img :src="verificationCodeImageSrc" alt="验证码加载失败" />
  <el-link class="mx-1" @click="updateVerificationCodeImage"
    >看不清楚, 换一张</el-link
  >
</div>
1
2
3
4
5
6

# 3.3 前端用户点击登录

登录表单的数据加上 uuid 和验证码,确保唯一性。

const form = reactive({
    username: '',
    password: '',
    uuid: '',
    verificationCode: '',
});
1
2
3
4
5
6

# 3.4 后端验证用户输入的验证码是否正确

@Override
public Result<AppUserLoginVO> userLogin(AppUserLoginDTO appUserLoginDTO) {
    if (Objects.nonNull(appUserLoginDTO)) {
        // 先对验证码进行核对
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        String uuid = appUserLoginDTO.getUuid();
        String verificationCode = appUserLoginDTO.getVerificationCode();
        String verificationCodeKey = "VerificationCode:" + uuid;
        // redis 里面存的验证码
        String redisVerificationCode = ops.get(verificationCodeKey);
        if (!Objects.equals(verificationCode, redisVerificationCode)) {
            // 用户填入的验证码与 redis 中的不相同
            return Result.success(new AppUserLoginVO(), "登录失败,验证码错误");
        } else {
            // 相同
            ops.getOperations().delete(verificationCodeKey);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 四、结果

# 五、参考资料