springboot图片验证码

article/2025/9/20 22:15:10

前言:

大家好!我是小小!今天我们用五分钟来用springboot实现我们常用的图形验证码功能模块!

用户登录几乎是一个线上系统必不可少且使用相对比较频繁的一个模块,为了防止恶意暴力尝试,防止洪水攻击、防止脚本自动提交等,验证码是一个较为便捷且行之有效的预防手段。

具体效果如下:

请添加图片描述

第一步:工具类

该工具类为生成验证码图片的核心,直接拷贝到项目即可,无需做修改;可个性化的参数全部对外提供的API,比如 字体大小背景颜色,干扰线数量高宽等都可以根据自己的需求设置对应参数;

代码几乎每一行都加了详细的注释;如果遇上特殊的个性化需求,调整一下这个工具类即可实现。

package com.feng.util;/*** @return null* @author Ladidol* @description* @date 2022/4/11 22:15*/import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.Random;/*** 图形验证码生成*/
public class VerifyUtil {// 默认验证码字符集private static final char[] chars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9','a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z','A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};// 默认字符数量private final Integer SIZE;// 默认干扰线数量private final int LINES;// 默认宽度private final int WIDTH;// 默认高度private final int HEIGHT;// 默认字体大小private final int FONT_SIZE;// 默认字体倾斜private final boolean TILT;private final Color BACKGROUND_COLOR;/*** 初始化基础参数** @param builder*/private VerifyUtil(Builder builder) {SIZE = builder.size;LINES = builder.lines;WIDTH = builder.width;HEIGHT = builder.height;FONT_SIZE = builder.fontSize;TILT = builder.tilt;BACKGROUND_COLOR = builder.backgroundColor;}/*** 实例化构造器对象** @return*/public static Builder newBuilder() {return new Builder();}/*** @return 生成随机验证码及图片* Object[0]:验证码字符串;* Object[1]:验证码图片。*/public Object[] createImage() {StringBuffer sb = new StringBuffer();// 创建空白图片BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);// 获取图片画笔Graphics2D graphic = image.createGraphics();// 设置抗锯齿graphic.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);// 设置画笔颜色graphic.setColor(BACKGROUND_COLOR);// 绘制矩形背景graphic.fillRect(0, 0, WIDTH, HEIGHT);// 画随机字符Random ran = new Random();//graphic.setBackground(Color.WHITE);// 计算每个字符占的宽度,这里预留一个字符的位置用于左右边距int codeWidth = WIDTH / (SIZE + 1);// 字符所处的y轴的坐标int y = HEIGHT * 3 / 4;for (int i = 0; i < SIZE; i++) {// 设置随机颜色graphic.setColor(getRandomColor());// 初始化字体Font font = new Font(null, Font.BOLD + Font.ITALIC, FONT_SIZE);if (TILT) {// 随机一个倾斜的角度 -45到45度之间int theta = ran.nextInt(45);// 随机一个倾斜方向 左或者右theta = (ran.nextBoolean() == true) ? theta : -theta;AffineTransform affineTransform = new AffineTransform();affineTransform.rotate(Math.toRadians(theta), 0, 0);font = font.deriveFont(affineTransform);}// 设置字体大小graphic.setFont(font);// 计算当前字符绘制的X轴坐标int x = (i * codeWidth) + (codeWidth / 2);// 取随机字符索引int n = ran.nextInt(chars.length);// 得到字符文本String code = String.valueOf(chars[n]);// 画字符graphic.drawString(code, x, y);// 记录字符sb.append(code);}// 画干扰线for (int i = 0; i < LINES; i++) {// 设置随机颜色graphic.setColor(getRandomColor());// 随机画线graphic.drawLine(ran.nextInt(WIDTH), ran.nextInt(HEIGHT), ran.nextInt(WIDTH), ran.nextInt(HEIGHT));}// 返回验证码和图片return new Object[]{sb.toString(), image};}/*** 随机取色*/private Color getRandomColor() {Random ran = new Random();Color color = new Color(ran.nextInt(256), ran.nextInt(256), ran.nextInt(256));return color;}/*** 构造器对象*/public static class Builder {// 默认字符数量private int size = 4;// 默认干扰线数量private int lines = 10;// 默认宽度private int width = 80;// 默认高度private int height = 35;// 默认字体大小private int fontSize = 25;// 默认字体倾斜private boolean tilt = true;//背景颜色private Color backgroundColor = Color.LIGHT_GRAY;public Builder setSize(int size) {this.size = size;return this;}public Builder setLines(int lines) {this.lines = lines;return this;}public Builder setWidth(int width) {this.width = width;return this;}public Builder setHeight(int height) {this.height = height;return this;}public Builder setFontSize(int fontSize) {this.fontSize = fontSize;return this;}public Builder setTilt(boolean tilt) {this.tilt = tilt;return this;}public Builder setBackgroundColor(Color backgroundColor) {this.backgroundColor = backgroundColor;return this;}public VerifyUtil build() {return new VerifyUtil(this);}}
}

第二步:图片生成:

使用默认参数:

//生成图片验证码
Object[] verify = VerifyUtil.newBuilder().build().createImage();

自定义参数生成:

// 这个根据自己的需要设置对应的参数来实现个性化
// 返回的数组第一个参数是生成的验证码,第二个参数是生成的图片
Object[] objs = VerifyUtil.newBuilder().setWidth(120)   //设置图片的宽度.setHeight(35)   //设置图片的高度.setSize(6)      //设置字符的个数.setLines(10)    //设置干扰线的条数.setFontSize(25) //设置字体的大小.setTilt(true)   //设置是否需要倾斜.setBackgroundColor(Color.WHITE) //设置验证码的背景颜色.build()         //构建VerifyUtil项目.createImage();  //生成图片

整合到springboot项目中:

需要引入的maven依赖:

        <!--redis相关配置--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- redis 连接池 --><!--新版本连接池lettuce--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><!-- 图形验证码 --><dependency><groupId>net.jodah</groupId><artifactId>expiringmap</artifactId><version>0.5.10</version></dependency>

获取相关的验证码:

service层:

package com.feng.service;import org.cuit.epoch.result.Result;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @return null* @author Ladidol* @description* @date 2022/4/11 22:15*/public interface VerifyService {/*** 创建图片验证码* @param response* @param request* @throws IOException*/void createCode(HttpServletResponse response, HttpServletRequest request) throws IOException;/*** 检查图片验证码* @param* @param* @throws IOException*/Result<String> checkCode(String verificationCode);
}

serviceimpl层:

package com.feng.service.impl;import com.feng.service.VerifyService;
import com.feng.util.RedisServiceImpl;
import com.google.common.net.HttpHeaders;import com.feng.util.VerifyUtil;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.time.Duration;/*** @return null* @author Ladidol* @description* @date 2022/4/11 22:15*/@Service
public class VerifyServiceImpl implements VerifyService {@ResourceRedisServiceImpl redisUtil;/*** 生成图片验证码* @param response* @param request* @throws IOException*/@Overridepublic void createCode(HttpServletResponse response, HttpServletRequest request) throws IOException {//获取sessionHttpSession session = request.getSession();//获得sessionIdString id = session.getId();System.out.println();ResponseCookie cookie = ResponseCookie.from("JSESSIONID",id).secure(true).domain("").path("/").maxAge(Duration.ofHours(1)).sameSite("None").build();//清除之前缓存的图片验证码if (!String.valueOf(request.getSession().getAttribute("SESSION_VERIFY_CODE_"+id)).isEmpty()){String getVerify = String.valueOf(request.getSession().getAttribute("SESSION_VERIFY_CODE_"+id));redisUtil.del(getVerify);System.out.println("清除成功");}//生成图片验证码,用的默认参数Object[] verify = VerifyUtil.newBuilder().build().createImage();//将验证码存入sessionsession.setAttribute("SESSION_VERIFY_CODE_" + id, verify[0]);//打印验证码System.out.println(verify[0]);//将验证码存入redisredisUtil.set((String) verify[0],id,5*60);//将图片传给浏览器BufferedImage image = (BufferedImage) verify[1];response.setContentType("image/png");response.setHeader(HttpHeaders.SET_COOKIE,cookie.toString());OutputStream ops = response.getOutputStream();ImageIO.write(image,"png",ops);}@Overridepublic Result<String> checkCode(String verificationCode){if (!redisUtil.hasKey(verificationCode)){return new Result<>(false,"验证码错误");}redisUtil.del(verificationCode);return R.success();}
}

这里面还会用到redis相关的工具类,我就不列出来了,想要的话可以看我以前的博客工具类戳这里

controller层:
这里有用到@RequiredArgsConstructor, 就是简单的注入而已, 如果想要详细了解戳这里


package com.feng.controller;import lombok.RequiredArgsConstructor;
import com.feng.annotation.LimitRequest;import com.feng.service.VerifyService;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @return null* @author Ladidol* @description 这里主要就是多种验证码和登录相关的东西* @date 2022/4/11 21:46*/
@RestController
@RequestMapping("/verify")
@RequiredArgsConstructor//这是在lombok工具给的注入方式,真帅
public class VerifyController {private final VerifyService verifyService;/*** 获取图片验证码*/@LimitRequest(count = 5)//这个注解就是表示, 你在限制时间里(我们这里默认是六秒钟), 只能请求五次@GetMapping("/getCode")public void getCode(HttpServletResponse response, HttpServletRequest request) throws IOException {verifyService.createCode(response, request);}@LimitRequest(count = 5)//这个注解就是表示, 你在限制时间里(我们这里默认是六秒钟), 只能请求五次@GetMapping("/checkCode")public Result<String> checkCode(String code){return verifyService.checkCode(code);}}

这里为了不被一直无限制的访问该服务, 我们用了一个限制ip访问次数的注解@LimitRequest

annotion包下的注解类:

package com.feng.annotation;import java.lang.annotation.*;/*** @return null* @author Ladidol* @description 限制ip访问次数注解* @date 2022/4/11 22:15*/@Documented
@Target(ElementType.METHOD) // 说明该注解只能放在方法上面
@Retention(RetentionPolicy.RUNTIME)
public @interface LimitRequest {long time() default 6000; // 限制时间 单位:毫秒int count() default 3; // 允许请求的次数}

aspect包下的切面类:

package com.feng.aspect;import net.jodah.expiringmap.ExpirationPolicy;
import net.jodah.expiringmap.ExpiringMap;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import com.feng.annotation.LimitRequest;
import org.cuit.epoch.exception.AppException;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;/*** @return null* @author Ladidol* @description* @date 2022/4/11 22:15*/@Aspect
@Component
public class LimitRequestAspect {private static ConcurrentHashMap<String, ExpiringMap<String, Integer>> book = new ConcurrentHashMap<>();// 定义切点// 让所有有@LimitRequest注解的方法都执行切面方法@Pointcut("@annotation(limitRequest)")public void excudeService(LimitRequest limitRequest) {}@Around("excudeService(limitRequest)")public Object doAround(ProceedingJoinPoint pjp, LimitRequest limitRequest) throws Throwable {// 获得request对象RequestAttributes ra = RequestContextHolder.getRequestAttributes();ServletRequestAttributes sra = (ServletRequestAttributes) ra;HttpServletRequest request = sra.getRequest();// 获取Map对象, 如果没有则返回默认值// 第一个参数是key, 第二个参数是默认值ExpiringMap<String, Integer> uc = book.getOrDefault(request.getRequestURI(), ExpiringMap.builder().variableExpiration().build());Integer uCount = uc.getOrDefault(request.getRemoteAddr(), 0);if (uCount >= limitRequest.count()) { // 超过次数,不执行目标方法System.out.println("接口请求超过次数!");throw new AppException("接口请求超过次数!");} else if (uCount == 0) { // 第一次请求时,设置有效时间
//uc.put(request.getRemoteAddr(), uCount + 1, ExpirationPolicy.CREATED, limitRequest.time(), TimeUnit.MILLISECONDS);} else { // 未超过次数, 记录加一uc.put(request.getRemoteAddr(), uCount + 1);}book.put(request.getRequestURI(), uc);// result的值就是被拦截方法的返回值Object result = pjp.proceed();return result;}
}

为了捕获全局的异常抛出, 且符合restful规范我们加一个这个处理类:

handle包下面的全局异常类:

package org.cuit.epoch.handler;import lombok.extern.log4j.Log4j2;import org.cuit.epoch.exception.AppException;
import org.cuit.epoch.result.R;
import org.cuit.epoch.result.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;@ControllerAdvice
@Log4j2
public class GlobalExceptionHandler {@ExceptionHandler(Exception.class)@ResponseBodypublic Result error(Exception e) {log.error(e.getMessage());e.printStackTrace();return R.fail(e.getMessage());}@ExceptionHandler(AppException.class)@ResponseBodypublic Result error(AppException e) {log.error(e.getMessage());e.printStackTrace();return R.fail(e.getMessage());}
}

application.yaml文件:

spring:cache:type:redisredis: #redis连接配置host: 自己redis的ip地址port: redis端口password: 密码jedis:pool:max-active: 8max-wait: -1msmax-idle: 500min-idle: 0lettuce:shutdown-timeout: 0ms

最终项目结构如下:

image-20220412172908356

先得到一个验证码:

image-20220411225740692

验证一下是否成功:

成功结果:

image-20220411225731282

验证失败结果:

image-20220412173201994

当请求在规定时间内的请求数超过规定的数量时或有报错:

image-20220412173025475

参考:

连接1

END

如果对springboot+springsecurity整合图形验证码感兴趣的话可以戳这里

这里有用到aspect, 博主后面会在写一篇来详细介绍aop在springboot中的实现

欢迎点赞关注哦!
也欢迎到访我的博客!小小的博客传送门!


http://chatgpt.dhexx.cn/article/Ka0w6peM.shtml

相关文章

JavaWeb总结之通过Servlet生成验证码图片

项目地址:https://github.com/zhangzeminzZ/ServletStudy 目录 1.BufferedImage类介绍2.在Form表单中使用验证码图片3.服务器端对form表单提交上来的验证码处理 1.BufferedImage类介绍 生成验证码图片主要用到了一个BufferedImage类 创建一个DrawImage Servlet&#xff0c;用…

网站安全检测之图片验证码

2019独角兽企业重金招聘Python工程师标准>>> 在对网站安全进行整体的安全检测的时候&#xff0c;用户登陆以及用户留言&#xff0c;评论&#xff0c;设置支付密码&#xff0c;以及一些网站功能方面都会用到图片验证码&#xff0c;针对于验证码我们SINE安全对其进行了…

实现图片验证码【详细代码】

实际开发过程中经常遇到要实现图片验证码来防止外部使用脚本刷接口&#xff0c;所以说图片验证码是很有必要的一个小功能。 html <!--- 注册页面整增加图形验证码功能,这里为了更贴近企业级业务&#xff0c;我们在注册页面整增加图形验证码功能--> <div class"u…

图片验证码实现的几种方式

一、Google Kaptcha 1、简介 kaptcha 是一个非常实用的验证码生成工具。有了它&#xff0c;你可以生成各种样式的验证码&#xff0c;因为它是可配置的。kaptcha工作的原理是调用 com.google.code.kaptcha.servlet.KaptchaServlet&#xff0c;生成一个图片。同时将生成的验证码…

ajax请求后台返回数据

功能介绍&#xff1a;最近学习在做一个新闻管理系统&#xff0c;其中有一个模块做的是一个排行榜功能&#xff0c;分为东部联盟和西部联盟&#xff0c;当我点击他的时候&#xff0c;排行的数据会发生变化。由于这一块怎么整个页面中的一小块&#xff0c;所以使用的是局部刷新页…

如何取消ajax请求

之前在面试的时候&#xff0c;被面试官问到了如何取消ajax请求&#xff0c;然鹅并不知道&#xff0c;被刷之后痛定思痛&#xff0c;总结了原生XHR、jquery、axios取消ajax请求的方法。 原生XHR 对于原生XHR对象来说&#xff0c;取消的ajax的关键是调用XHR对象的.abort()方法 …

HTML AJAX请求调用

html ajax请求 ------------------温故而知新,可以装逼矣-------------------- 大佬提出需求&#xff0c;访问html文件&#xff0c;动态响应文章内容 作为一个纯正&#xff08;半吊子&#xff09;的JAVA后端写太多前后端分离的项目&#xff0c;太久没接触前端知识。头都是炸的…

ajax请求五个步骤!

ajax请求五个步骤&#xff01; 今天 咱们讲一讲Ajax请求五个步骤&#xff01; 1.创建XMLHttpRequest异步对象 var xhr; if (window.XMLHttpRequest){// code for IE7, Firefox, Chrome, Opera, Safarixhrnew XMLHttpRequest();} else{// code for IE6, IE5xhrnew ActiveXObje…

jquery(五)Ajax请求

在jQuery中AJAX的写法有3种&#xff0c;$ajax&#xff0c;$post&#xff0c;$get这三种。其中$post和$get是简易写法&#xff0c;高层的实现&#xff0c;在调用他们的时候&#xff0c;会运行底层封装好的$ajax。 ajax写法 $.ajax({url:"http://www.microsoft.com",…

Ajax请求参数

Ajax请求参数 GET请求参数的传递创建服务器通过表单访问服务器 POST请求参数的传递application/x-www-form-urlencoded参数的接收与发送创建服务器通过表单访问服务器 JSON格式数据的发送与接收创建服务器通过表单访问服务器 GET请求参数的传递 设置open()方法中的第1个参数为…

Ajax请求以及发送Ajax请求的方式

1.在写C语言时候&#xff0c;写了好长时间&#xff0c;终于开始运行了&#xff0c;结果出现了这种情况&#xff0c;以前就没见过。 原来是自己的函数名写错了&#xff0c;main写成了amin.所以写代码一定要细心&#xff0c;不然会给你带来很失望的心情。 2.Ajax请求步骤 //1.创…

html的ajax请求

页面中ajax发起请求&#xff0c;controller接收数据并处理 这次ajax的测试以访问html的方式配置 配置yml文件&#xff1a; 然后在 src/main/webapp下创建一个html页面 创建controller&#xff1a; 运行项目就可以访问到index.html&#xff1a; 引入jquery&#xff1a; 修改…

Ajax请求详解

Ajax请求 一、什么是Ajax二、Ajax原理是什么三、Ajax的使用1.创建Ajax核心对象XMLHttpRequest(记得考虑兼容性)2.向服务器发送请求3.服务器响应处理&#xff08;区分同步跟异步两种情况&#xff09;①同步处理②异步处理③GET和POST请求数据区别 四、结束语五、jQuery的ajax函数…

Ajax原理一篇就够了

前言 AJAX即“Asynchronous Javascript And XML”,是指一种创建交互式网页应用的网页开发技术。AJAX 是一种用于创建快速动态网页的技术。它可以令开发者只向服务器获取数据(而不是图片,HTML文档等资源),互联网资源的传输变得前所未有的轻量级和纯粹,这激发了广大开发者的…

Ajax请求

ajax请求简介&#xff1a; ajax是利用前端的技术&#xff0c;向服务器发送一个异步请求 原有的请求在请求后需要刷新整张页面&#xff0c;但是用ajax请求后只需要刷新一部分页面即可&#xff1b; xhr基础属性&#xff1a; Jquery与ajax&#xff1a;使用$.ajax({}) Ajax的html…

Ajax请求的五个步骤

目录 Ajax请求的五个步骤 一、定义 1、什么是Ajax 2、同步与异步的区别 3、ajax的工作原理 二、实现AJAX的基本步骤 1、创建XMLHttpRequest对象 2、创建HTTP请求 3、设置响应HTTP请求状态变化的函数 4、设置获取服务器返回数据的语句 5、发送HTTP请求 6、局部更新 …

详解Ajax请求

目录 1.$.get()函数的语法 2.$.get()发起不带参数的请求 3.$.get()发起带参数的请求 4.$.post()函数的语法 5.$.post()向服务器提交数据 6.$.ajax()函数的语法 7.使用$.ajax()发起GET请求 ​8.使用$.ajax()发起POST请求 1.$.get()函数的语法 jQuery中$.get()函数的功能单…

linux下输入法,中英文自由切换,仍无法输入中文

1、安装中文输入法&#xff1a;yum install "Chinese Support" 2、中英文切换&#xff1a;Super空格 &#xff08;其中Super键在Ctrl和Alt之间&#xff09; 也可以直接点击右上角进行切换 3、设置输入源&#xff1a;如果直接选择汉语的话&#xff0c;即使中英文可以…

Linux配置中文输入法图文教程

原来的设置中&#xff0c;从中文切换到英文或者从英文切换为中文&#xff08;跨语言&#xff09;时&#xff0c;总要使用不同的快捷键。在这个教程中&#xff0c;我们将使用controlshift来完成所有输入法的切换&#xff08;无论是否跨语言&#xff09;。 首先在键盘设置中&…

「 Linux 」“安装中文输入法方法”讲解

一、前言 每重装一次Linux系统都会被中文输入法打扰一下&#xff0c;把安装方法记录下来吧&#xff0c;方便操作。 二、技术实现 1. 点击屏幕右上角的螺丝口&#xff0c;选择“System Settings”&#xff1b; 2. 点击“Language suport”&#xff1a; 3. 弹出语言支持安装窗口…