Java 生成图形验证码

一、图形验证码的意义

图形验证码是一种广泛应用于互联网领域的安全验证机制,它通过向用户展示包含字符、数字、图形等信息的图片,要求用户正确识别并输入其中的内容,以此来区分用户是人类还是机器程序。图形验证码具有多方面重要意义:

  1. 防止恶意注册和暴力破解:在网站或应用的注册、登录环节,图形验证码能有效抵御恶意程序的自动化操作。例如,一些黑客试图使用脚本程序批量注册账号,以进行垃圾邮件发送、刷取积分等非法活动。图形验证码的存在使得这些自动化程序难以准确识别和输入验证内容,从而保护网站的用户数据安全,维护正常的注册和登录秩序。同理,在登录时,若没有图形验证码,黑客可能通过暴力破解手段,利用程序不断尝试不同的密码组合来获取用户账号权限。图形验证码增加了破解的难度,保护了用户的账号安全。
  2. 抵御网络爬虫和数据抓取:许多网站为了保护自身数据的安全性和知识产权,不希望其内容被随意抓取。网络爬虫程序可以自动访问网站并提取大量数据。图形验证码的设置使得爬虫程序无法顺利通过验证,从而限制了它们对网站数据的非法获取。例如,新闻网站、电商平台等都依赖图形验证码来防止竞争对手或恶意用户通过爬虫获取其重要信息,如新闻内容、商品价格、用户评价等。
  3. 防止刷票和作弊行为:在各种投票活动、抢购活动中,图形验证码能确保活动的公平性。以在线投票为例,如果没有图形验证码,有人可能会编写程序进行刷票,使投票结果失去真实性和公正性。而加入图形验证码后,每个投票行为都需要用户手动识别并输入验证码,增加了刷票的难度,保证了投票结果能真实反映公众的意愿。在抢购限量商品或优惠券时,图形验证码也能防止机器人程序瞬间抢光商品,让普通用户有公平的机会参与抢购。
  4. 保护网站服务器资源:恶意程序的大量自动化请求可能会给网站服务器带来巨大的负担,甚至导致服务器瘫痪。图形验证码可以过滤掉这些非法的请求,减轻服务器的压力,确保网站能够正常运行,为合法用户提供稳定的服务。例如,一些小型网站如果遭受大量恶意程序的攻击,可能会因为服务器资源耗尽而无法访问,图形验证码在一定程度上可以避免这种情况的发生。
  5. 提供用户身份验证的额外保障:除了用户名和密码等常规的身份验证方式外,图形验证码作为一种额外的验证手段,增加了用户身份验证的安全性。即使黑客获取了用户的密码,由于无法通过图形验证码的验证,也无法成功登录账号,从而进一步保护了用户的个人信息和资产安全。

今天,我们来分析一下 vue3-element-admin 前后端代码,以便抽离出一套通用的图形验证码实现。vue3-element-admin 的前后端环境搭建,可以看我的博文

让我们致敬开源的力量!

二、实现思路

三、代码分析

  • 前端 src/views/login/index.vue
javascript 复制代码
onMounted(() => {
  getCaptcha();
});
  • 请求后端 com.youlai.boot.shared.auth.controller.AuthController#getCaptcha 接口
java 复制代码
/**
     * 获取验证码
     *
     * @return 验证码
     */
    @Override
    public CaptchaInfo getCaptcha() {

        String captchaType = captchaProperties.getType();
        int width = captchaProperties.getWidth();
        int height = captchaProperties.getHeight();
        int interfereCount = captchaProperties.getInterfereCount();
        int codeLength = captchaProperties.getCode().getLength();

        // 调用 hutool 验证码工具,生成验证码
        AbstractCaptcha captcha;
        if (CaptchaTypeEnum.CIRCLE.name().equalsIgnoreCase(captchaType)) {
            captcha = CaptchaUtil.createCircleCaptcha(width, height, codeLength, interfereCount);
        } else if (CaptchaTypeEnum.GIF.name().equalsIgnoreCase(captchaType)) {
            captcha = CaptchaUtil.createGifCaptcha(width, height, codeLength);
        } else if (CaptchaTypeEnum.LINE.name().equalsIgnoreCase(captchaType)) {
            captcha = CaptchaUtil.createLineCaptcha(width, height, codeLength, interfereCount);
        } else if (CaptchaTypeEnum.SHEAR.name().equalsIgnoreCase(captchaType)) {
            captcha = CaptchaUtil.createShearCaptcha(width, height, codeLength, interfereCount);
        } else {
            throw new IllegalArgumentException("Invalid captcha type: " + captchaType);
        }
        captcha.setGenerator(codeGenerator);
        captcha.setTextAlpha(captchaProperties.getTextAlpha());
        captcha.setFont(captchaFont);
        // 验证码
        String captchaCode = captcha.getCode();
        // Base64 验证码图形字符串
        String imageBase64Data = captcha.getImageBase64Data();

        // 生成UUID的RedisKey,并返回给前端
        String captchaKey = IdUtil.fastSimpleUUID();
        // 验证码文本缓存至Redis,用于登录校验
        redisTemplate.opsForValue().set(
                StrUtil.format(RedisConstants.Captcha.IMAGE_CODE, captchaKey),
                captchaCode,
                captchaProperties.getExpireSeconds(),
                TimeUnit.SECONDS
        );

        return CaptchaInfo.builder()
                .captchaKey(captchaKey)
                .captchaBase64(imageBase64Data)
                .build();
    }
--------------------------------------------------------------------------------------
@Builder
public class CaptchaInfo {

    @Schema(description = "验证码缓存 Key")
    private String captchaKey;

    @Schema(description = "验证码图片Base64字符串")
    private String captchaBase64;

}
--------------------------------------------------------------------------------------
  • 前端登录的时候 src/api/auth/index.ts
javascript 复制代码
const AUTH_BASE_URL = "/api/v1/auth";
const AuthAPI = {
  /** 登录接口*/
  login(data: LoginFormData) {
    const formData = new FormData();
    // 用户名
    formData.append("username", data.username);
    // 密码 
    formData.append("password", data.password);
    // 验证码 RedisKey
    formData.append("captchaKey", data.captchaKey);
    // 用户输入的验证码
    formData.append("captchaCode", data.captchaCode);
    return request<any, LoginResult>({
      url: `${AUTH_BASE_URL}/login`,
      method: "post",
      data: formData,
      headers: {
        "Content-Type": "multipart/form-data",
      },
    });
  },
  • 后端过滤器 com.youlai.boot.core.security.filter.CaptchaValidationFilter#doFilterInternal
java 复制代码
@Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        // 检验登录接口的验证码
        if (LOGIN_PATH_REQUEST_MATCHER.matches(request)) {
            // 请求中的验证码
            String captchaCode = request.getParameter(CAPTCHA_CODE_PARAM_NAME);
            // TODO 兼容没有验证码的版本(线上请移除这个判断)
            if (StrUtil.isBlank(captchaCode)) {
                chain.doFilter(request, response);
                return;
            }
            // 缓存中的验证码
            String verifyCodeKey = request.getParameter(CAPTCHA_KEY_PARAM_NAME);
            String cacheVerifyCode = (String) redisTemplate.opsForValue().get(
                    StrUtil.format(RedisConstants.Captcha.IMAGE_CODE, verifyCodeKey)
            );
            if (cacheVerifyCode == null) {
                // 过期报错提示
                ResponseUtils.writeErrMsg(response, ResultCode.USER_VERIFICATION_CODE_EXPIRED);
            } else {
                // 验证码比对
                if (codeGenerator.verify(cacheVerifyCode, captchaCode)) {
                    // 进入登录校验接口
                    chain.doFilter(request, response);
                } else {
                    // 验证码不同报错提示
                    ResponseUtils.writeErrMsg(response, ResultCode.USER_VERIFICATION_CODE_ERROR);
                }
            }
        } else {
            // 非登录接口放行
            chain.doFilter(request, response);
        }
    }
  • 登录校验接口 com.youlai.boot.shared.auth.controller.AuthController#login
java 复制代码
    @Operation(summary = "账号密码登录")
    @PostMapping("/login")
    @Log(value = "登录", module = LogModuleEnum.LOGIN)
    public Result<AuthenticationToken> login(
            @Parameter(description = "用户名", example = "admin") @RequestParam String username,
            @Parameter(description = "密码", example = "123456") @RequestParam String password
    ) {
        AuthenticationToken authenticationToken = authService.login(username, password);
        return Result.success(authenticationToken);
    }
相关推荐
AI人工智能+电脑小能手7 分钟前
【大白话说Java面试题】【Java基础篇】第22题:HashMap 和 HashSet 有哪些区别
java·开发语言·哈希算法·散列表·hash
juniperhan21 分钟前
Flink 系列第21篇:Flink SQL 函数与 UDF 全解读:类型推导、开发要点与 Module 扩展
java·大数据·数据仓库·分布式·sql·flink
ID_1800790547323 分钟前
Python 实现亚马逊商品详情 API 数据准确性校验(极简可用 + JSON 参考)
java·python·json
c++之路43 分钟前
C++23概述
java·c++·c++23
专注API从业者2 小时前
Open Claw 京东商品监控选品实战:一键抓取、实时监控、高效选品
java·服务器·数据库
摇滚侠2 小时前
DBeaver 导入数据库 导入 SQL 文件 MySQL 备份恢复
java·数据库·mysql
keep one's resolveY2 小时前
SpringBoot实现重试机制的四种方案
java·spring boot·后端
天空属于哈夫克33 小时前
企业微信API常见的错误和解决方案
java·数据库·企业微信
摇滚侠4 小时前
VMvare 虚拟机 Oracle19c 安装步骤,远程连接 Oracle19c,百度网盘安装包
java·oracle
梁萌4 小时前
idea报错找不到XX包的解决方法
java·intellij-idea·启动报错·缺少包