【验证码】在自己项目中灵活引入验证码(从若依中剥离)

news2024/10/21 19:11:27

本次写这篇文章是系统中有几处登录时没有添加验证码校验,导致安全等保时扫描出高危漏洞。尽管接口已经做了限制登录(5次密码错误,锁定账号)。既然扫出了高危,那也是要想办法解决,这里选择在登录时添加后端验证码校验,这样就可以防止登录时暴力破解

验证码的作用

验证码是为了区分计算机程序(如自动化机器人、脚本等)和真实的人类用户。其目的是保护网站和服务免受自动化攻击和滥用,确保交互是由真正的用户发起的。

  • 1.防止自动化攻击:

    • 防止垃圾邮件: 验证码可以阻止自动化程序(如垃圾邮件机器人)发送大量垃圾邮件。
    • 防止恶意注册: 通过验证码,可以防止自动化程序批量注册虚假账户。
    • 防止恶意登录: 验证码可以增加暴力破解密码的难度,防止自动化程序尝试大量密码组合。
  • 2.保护用户隐私:

    • 防止数据抓取: 验证码可以防止自动化程序抓取网站上的敏感数据。
    • 防止恶意爬虫: 验证码可以阻止恶意爬虫抓取网站内容,保护网站的知识产权。
  • 3.提高安全性:

    • 防止DDoS攻击: 验证码可以增加攻击者发起分布式拒绝服务(DDoS)攻击的难度。
    • 防止自动化脚本滥用: 验证码可以防止自动化脚本滥用网站功能,如自动发布评论、点赞等。

虽然验证码可能会给用户带来一些不便,但它提供了一种简单且有效的方式来验证用户身份

使用

这里借鉴若依的验证码,若依的验证码是比较简单且好用的一个功能,而且基本上所有项目都能很容易引入

1. 引入pom依赖

<!-- 验证码 -->
<dependency>
	<groupId>pro.fessional</groupId>
	<artifactId>kaptcha</artifactId>
	<version>2.3.3</version>
	<exclusions>
		<exclusion>
			<artifactId>servlet-api</artifactId>
			<groupId>javax.servlet</groupId>
		</exclusion>
	</exclusions>
</dependency>

2. 导入配置类

package com.sun3d.why.config;

import java.util.Random;
import com.google.code.kaptcha.text.impl.DefaultTextCreator;

/**
 * 验证码文本生成器
 *
 * @author shangtf
 */
public class KaptchaTextCreator extends DefaultTextCreator
{
    private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");

    @Override
    public String getText()
    {
        Integer result = 0;
        Random random = new Random();
        int x = random.nextInt(10);
        int y = random.nextInt(10);
        StringBuilder suChinese = new StringBuilder();
        int randomoperands = random.nextInt(3);
        if (randomoperands == 0)
        {
            result = x * y;
            suChinese.append(CNUMBERS[x]);
            suChinese.append("*");
            suChinese.append(CNUMBERS[y]);
        }
        else if (randomoperands == 1)
        {
            if ((x != 0) && y % x == 0)
            {
                result = y / x;
                suChinese.append(CNUMBERS[y]);
                suChinese.append("/");
                suChinese.append(CNUMBERS[x]);
            }
            else
            {
                result = x + y;
                suChinese.append(CNUMBERS[x]);
                suChinese.append("+");
                suChinese.append(CNUMBERS[y]);
            }
        }
        else
        {
            if (x >= y)
            {
                result = x - y;
                suChinese.append(CNUMBERS[x]);
                suChinese.append("-");
                suChinese.append(CNUMBERS[y]);
            }
            else
            {
                result = y - x;
                suChinese.append(CNUMBERS[y]);
                suChinese.append("-");
                suChinese.append(CNUMBERS[x]);
            }
        }
        suChinese.append("=?@" + result);
        return suChinese.toString();
    }
}
package com.sun3d.why.config;

import java.util.Properties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import static com.google.code.kaptcha.Constants.*;

/**
 * 验证码配置
 * 
 * @author shangtf
 */
@Configuration
public class CaptchaConfig
{
    @Bean(name = "captchaProducer")
    public DefaultKaptcha getKaptchaBean()
    {
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        // 是否有边框 默认为true 我们可以自己设置yes,no
        properties.setProperty(KAPTCHA_BORDER, "yes");
        // 验证码文本字符颜色 默认为Color.BLACK
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
        // 验证码图片宽度 默认为200
        properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
        // 验证码图片高度 默认为50
        properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
        // 验证码文本字符大小 默认为40
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38");
        // KAPTCHA_SESSION_KEY
        properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode");
        // 验证码文本字符长度 默认为5
        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
        // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
        // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
        properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }

    @Bean(name = "captchaProducerMath")
    public DefaultKaptcha getKaptchaBeanMath()
    {
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        // 是否有边框 默认为true 我们可以自己设置yes,no
        properties.setProperty(KAPTCHA_BORDER, "yes");
        // 边框颜色 默认为Color.BLACK
        properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
        // 验证码文本字符颜色 默认为Color.BLACK
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
        // 验证码图片宽度 默认为200
        properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
        // 验证码图片高度 默认为50
        properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
        // 验证码文本字符大小 默认为40
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
        // KAPTCHA_SESSION_KEY
        properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
        // 验证码文本生成器
        properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.sun3d.why.config.KaptchaTextCreator");
        // 验证码文本字符间距 默认为2
        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
        // 验证码文本字符长度 默认为5
        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
        // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
        // 验证码噪点颜色 默认为Color.BLACK
        properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
        // 干扰实现类
        properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
        // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
        properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

注意: 在这个配置类中需要在验证码文本生成器中指定上面那个文本生成器类的路径!!!

3. 业务中使用

3.1 引入bean

将上述两个bean引入,一个是算术验证码,一个图形验证码

import com.google.code.kaptcha.Producer;

    @Autowired
    @Qualifier("captchaProducer")
    private Producer captchaProducer;

    @Autowired
    @Qualifier("captchaProducerMath")
    private Producer captchaProducerMath;
3.2 获取验证码接口
import javax.imageio.ImageIO;
import com.sun3d.why.util.Base64;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;

    /**
     * 生成验证码
     */
    @RequestMapping("/captchaImage")
    @ResponseBody
    public Map getCode() throws IOException
    {
        Map result = new HashMap();
        // 保存验证码信息
        String uuid = UUIDUtils.createUUId();
        String verifyKey = "captcha_codes:" + uuid;
        String capStr = null, code = null;
        BufferedImage image = null;
        // 此值可以配置为math或char
        String captchaType = "char";
        if ("math".equals(captchaType))
        {
            // 生成验证码
            String capText = captchaProducerMath.createText();
            capStr = capText.substring(0, capText.lastIndexOf("@"));
            code = capText.substring(capText.lastIndexOf("@") + 1);
            image = captchaProducerMath.createImage(capStr);
        }
        else if ("char".equals(captchaType))
        {
            capStr = code = captchaProducer.createText();
            image = captchaProducer.createImage(capStr);
        }

        ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
        opsForValue.set(verifyKey, code, 2, TimeUnit.MINUTES);
        // 转换流信息写出
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        try
        {
            ImageIO.write(image, "jpg", os);
        }
        catch (IOException e)
        {
            result.put("code", "fail");
            return result;
        }
        result.put("code", "success");
        result.put("uuid", uuid);
        result.put("img", Base64.encode(os.toByteArray()));
        return result;
    }

1. 这里将验证码的结果存入到了redis中,过期时间为2分钟。并将uuid,以及图片返回给了前端。
2. 切换 captchaType 的值可以改变验证码的类型,将此值配置到配置文件中,可灵活改变验证码类型

上面使用ByteArrayOutputStream 类来输出图片,若依中使用的效率更好的FastByteArrayOutputStream类,但是由于我的项目过于老旧,就没有使用这个


FastByteArrayOutputStream类是始于springframework框架4.2版本之后的,很明显我的项目不允许使用。

3.3 校验验证码

在添加验证码的业务中校验验证码,先校验验证码,如果验证码校验通过才去执行后面的逻辑,校验失败直接返回,不在执行逻辑

    /**
     * 校验验证码
     *
     * @param code 验证码
     * @param uuid 唯一标识
     * @return 结果
     */
    public boolean validateCaptcha(String code, String uuid) {
        if (StringUtils.isEmpty(uuid)) {
            uuid = "";
        }
        String verifyKey = "captcha_codes:" + uuid;
        ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
        String captcha = opsForValue.get(verifyKey);
        // 删除验证码
        redisTemplate.delete(verifyKey);
        if (captcha == null) {
            return false;
        }
        if (!code.equalsIgnoreCase(captcha)) {
            return false;
        }
        return true;
    }

这里使用过之后做了删除redis的操作很重要,可见若依想的还是比较全面!

使用上面方法校验

	// 登录方法
    public Map terminalLogin(CmsTerminalUser user,String code,String uuid) {

        Map<String, Object> map = new HashMap<String, Object>();
        boolean flag = validateCaptcha(code, uuid);
        if (!flag){
            map.put("status", RESULT_STR_FAILURE);
            map.put("msg", "验证码错误");
            return map;
        }

		//...登录逻辑......
	}
3.4 前端使用时注意

前端使用时注意:

<img id="captchaImage" onclick="getCaptchaImage()" style="display: inline" src="">



function getCaptchaImage(){
	$.ajax({
		type: "POST",
		url: '${path}/frontTerminalUser/captchaImage.do',
		success: function (data) {
			$("#captchaImage").attr("src","data:image/gif;base64,"+data.img);
			$("#uuid").val(data.uuid);
		},
		error: function () {
		}
	});
}

这里注意src的值拼接时,需要拼接"data:image/gif;base64,"字符串。

上面是jsp页面的写法,如果是vue按照vue的方式给img标签赋值方式即可!

注意:在提交登录接口时要将刚才请求验证码时返回的uuid以及用户输入的结果传给后端。


˚‧º·(˚ ˃̣̣̥᷄⌓˂̣̣̥᷅ )‧º·˚ 唉,使用若依框架的人真幸福

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2220233.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【电商项目】1分布式基础篇

1 项目简介 1.2 项目架构图 1.2.1 项目微服务架构图 1.2.2 微服务划分图 2 分布式基础概念 3 Linux系统环境搭建 查看网络IP和网关 linux网络环境配置 补充P123&#xff08;修改linux网络设置&开启root密码访问&#xff09; 设置主机名和hosts映射 主机名解析过程分析&…

金九银十互联网大厂Java高频面试题(2024最新含答案)

2024 年的互联网行业竞争越来越严峻&#xff0c;面试也是越来越难&#xff0c;一直以来我都想整理一套完美的面试宝典&#xff0c;奈何难抽出时间&#xff0c;这套 1200道的 Java 面试手册我整理了整整 1 个月&#xff0c;上传到 Git 上目前 star 数达到了 30K 这套互联网 Jav…

冲击美团!已成功 OC

这是一位训练营学员的美团面经&#xff0c;目前已经 OC 。 在此之前他已经拿到了不少公司的offer&#xff0c;但是都达不到他的预期&#xff0c;美团给的待遇就非常不错&#xff0c;大厂不愧是大厂&#xff0c;就是不知道工作强度如何。 他经历了一共三场面试&#xff0c;一面…

实现省略号查看详情样式

1.期望实现效果 2.目前实现效果 3.实现代码 1.wxml<view class"desc-text"><view class"show-more">查看详情 >></view><!-- <rich-text nodes"{{富文本接口数据内容 }}"></rich-text> --><text&…

python反爬

1.无限debug无法f12 关闭掉 Deactivate Breakpoints

【闲谈程序设计例三则:抛弃传统单步进初级阶段,用推导归纳出来的规律写代码,进入进阶阶段,人类自性的高级活动。】2024-10-21

闲谈程序设计三则&#xff1a;抛弃传统单步进&#xff0c;用推导归纳出来的规律写代码。 本论坛常见新学提问都是一些入门级别的问题&#xff0c;近来AI活跃抢答&#xff0c;然而&#xff0c;对于有些问题AI可以说是答非所问&#xff0c;令人哭笑不得&#xff0c;而AI能回答的…

MacOS安装BurpSuite

文章目录 一、下载地址二、下载注册机三、安装教程四、启动burpsuit五、免责声明 一、下载地址 https://portswigger-cdn.net/burp/releases/download?productpro&version2024.7.1&typeMacOsx二、下载注册机 https://github.com/NepoloHebo/BurpSuite-BurpLoaderKey…

B站协议登录到实现各种功能完整代码(专栏总结)

B站协议登录、点赞、收藏、转发实现及代码 关注、动态转发实现动态抽奖实现及代码 直播预约抽奖实现及代码 本文为本专栏的总结文章 一、扫码登录 请求获取二维码包&#xff0c;得到二维码链接和qrcode_key参数之后&#xff0c;利用qrcode_key循环GET请求登录状态包即可&#x…

【word】页眉横线无法取消

小伙伴们日常想在页眉里加横线&#xff0c;直接双击页眉&#xff0c;然后在页眉横线里选择自己喜欢的横线样式就可以了。 但今天我遇到的这个比较奇特&#xff0c;有些页有这个横线&#xff0c;有些页没有&#xff0c;就很奇怪。 最后排查完&#xff0c;发现是只有标题2的页…

WPF入门_02依赖属性

1、依赖属性主要有以下三个优点 1)依赖属性加入了属性变化通知、限制、验证等功能。这样可以使我们更方便地实现应用,同时大大减少了代码量 2)节约内存:在WinForm中,每个UI控件的属性都赋予了初始值,这样每个相同的控件在内存中都会保存一份初始值。而WPF依赖属性很好地…

《向量数据库指南》揭秘:Mlivus Cloud如何赋能GraphRAG应用

嘿,各位向量数据库和AI领域的探索者们,我是你们的老朋友,大禹智库的向量数据库高级研究员王帅旭,也是《向量数据库指南》的作者。今天,咱们来聊聊一个既前沿又实用的话题——检索增强生成(Retrieval Augmented Generation,简称RAG)及其面临的挑战,特别是如何用Mlivus …

前端/node.js锁定依赖版本、锁定依赖的依赖的版本

一、知识前提 version&#xff1a;必须依赖某个具体的版本。如&#xff1a;vue的3.2.0&#xff0c;表示必须安装3.2.0版本。>version&#xff1a;必须大于某个版本。>version&#xff1a;大于或等于某个版本。<version&#xff1a;必须小于某个版本。<version&…

多线程——单例模式

目录 前言 一、设计模式 二、饿汉模式 三、懒汉模式 1.单线程版 2.多线程版 结尾 前言 前面的几篇文章中介绍了多线程编程的基础知识&#xff0c;在本篇文章开始&#xff0c;就会利用前面的多线程编程知识来编写一些代码案例&#xff0c;从而使大家可以更好的理解运用多…

扩散模型对抗蒸馏:ADD 和 Latent-ADD

扩散模型对抗蒸馏&#xff1a;ADD 和 Latent-ADD ADD&#xff08;Adversarial Diffusion Distillation&#xff09;和 Latent-ADD 是 StabilityAI 公司提出的一系列针对 Stable Diffusion 的扩散模型对抗蒸馏方法&#xff0c;通过对抗训练和蒸馏训练来提高扩散模型的采样速度&…

python基于图片内容识别的微信自动发送信息(对其中逻辑修改一些可以改为自动化回复)

1.内容基于python日常生活问题帮助 2.主要框架 import time from datetime import datetimeimport pyperclip import win32api import win32con import os import refrom Image_Content_Text_Recognition import ICTR from screenshot import img 上面是逻辑部分主要框架 i…

【开源免费】基于SpringBoot+Vue.JS在线视频教育平台(JAVA毕业设计)

本文项目编号 T 027 &#xff0c;文末自助获取源码 \color{red}{T027&#xff0c;文末自助获取源码} T027&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 新…

解析 Vue 模板的本质:从语法糖到渲染过程

大家耳熟能详的表述如下&#xff1a;Vue 模板的本质其实是一种 声明式渲染 的形式&#xff0c;它在开发过程中提供了将组件的结构与逻辑分离的便利。 也就是说&#xff0c;模板 template 的存在只是为了让我们以更直观的方式描述界面的结构&#xff0c;然而在运行时&#xff0…

Android Framework AMS(09)service组件分析-3(bindService和unbindService关键流程分析)

该系列文章总纲链接&#xff1a;专题总纲目录 Android Framework 总纲 本章关键点总结 & 说明&#xff1a; 说明&#xff1a;上上一章节主要解读应用层service组件启动的2种方式startService和bindService&#xff0c;以及从APP层到AMS调用之间的打通。上一章节我们关注了s…

北京大学冯惠:与卓越者同行,方能更快的成长 | OceanBase数据库大赛获奖选手访谈

本文邀请2022 OceanBase 数据库大赛的季军&#xff0c;来自北京大学的冯惠同学&#xff0c;与我们分享如何寻找自己的兴趣&#xff1b;在一番经历后&#xff0c;对于产品与研发的职业方向观察&#xff1b;以及如何在学生时期提升个人专业能力&#xff0c;和参加数据库大赛的个人…

【Python技术】利用akshare定时获取股票实时价,低于5日线钉钉通知报警

今天看了下大盘&#xff0c;临时有个想法&#xff0c;我想知道某个股票回踩5日线的价格&#xff0c;如果实时价格低于5日线通过钉钉报警通知我。 说干就干&#xff0c;临时撸了下简单的代码&#xff0c;仅做演示。 1、计算5日线思路 很多券商软件的MA5价格是近5个交易日收盘…