前言:

大家好!我是小小!今天我们用五分钟来用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

最终项目结构如下:

先得到一个验证码:

验证一下是否成功:

成功结果:

验证失败结果:

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

参考:

连接1

END

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

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

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

springboot图片验证码相关推荐

  1. Shiro安全框架(Shiro与SpringBoot整合开发)之图片验证码(五)

    这篇博文主要讲解实现图片验证码的实现,之前已经将权限,认证,自带缓存,redis缓存,都实现了,现在我们就来了解一下简单的图片验证码的实现: 首先,我们要来修改一下登录的页面,login.jsp这个页 ...

  2. springboot添加图片验证码(拦截器)

    先说下验证码的最基本思路.首先在后端生成随机验证码,存入session.前端接收后端生成的验证码图片或随机字符,展示给用户.用户输入验证码,提交时发送到后台,与session中的验证码进行比较.下面代 ...

  3. bufferedimage生成的图片模糊_Kaptcha图片验证码工具

    阅读文本大概需要3分钟. 验证码的作用 图片验证码自从诞生以来从未被抛弃,依然发出属于它所应有的光.验证码经常验证如下一些场景. 1.用户登录,防止机器人登录 2.论坛留言,防止恶意灌水 3.短信验证 ...

  4. html登录图片验证码的实现

    流程:验证码图片由服务器的java后端生成,前端向后端请求图片验证码,项目工程使用了springboot框架 生成图片验证码的工具类: package com.main.activity.verify ...

  5. Spring Boot整合Shiro + JSP教程(用户认证,权限管理,图片验证码)

    在此首先感谢**编程不良人**up主提供的视频教程 代码都是跟着up的视频敲的,遇到的一些问题也是通过CSDN博主提供的教程解决的,在此也感谢那些提供bug解决方案的前辈们~ 项目完整代码已经发布到g ...

  6. 图片验证码的实现-kaptcha

    [注意]:适用与springboot项目 1.加载jar包.由于groupId的不同,图片验证码的样式会有所不同 <!--计算类型的验证码 --><dependency>< ...

  7. Kaptcha图片验证码工具

    阅读文本大概需要3分钟. 验证码的作用 图片验证码自从诞生以来从未被抛弃,依然发出属于它所应有的光.验证码经常验证如下一些场景. 1.用户登录,防止机器人登录 2.论坛留言,防止恶意灌水 3.短信验证 ...

  8. Java 图片验证码/图形验证码 亲测

    Java 图片验证码/图形验证码 1. Maven包 pom.xml 2. 图片验证码工具类 3. 生成验证码,图片验证码 和 验证接口 4. 验证页面 index.html 1. Maven包 po ...

  9. Spring boot+ Spring security 实现图片验证码验证

    springboot+security实现用户权限管理后,登陆要求增加图片验证码 pring security使用众多的过滤器对url进行拦截,以此来进行权限管理.Spring security不允许 ...

  10. java爬取验证码图片_JAVA HttpClient实现页面信息抓取(获取图片验证码并传入cookie实现信息获取)...

    JAVA HttpClient实现页面信息抓取(获取图片验证码并传入cookie实现信息获取) 发布时间:2018-05-18 16:41, 浏览次数:632 , 标签: JAVA HttpClien ...

最新文章

  1. ubuntu开机出现:system program problem detected
  2. java多线程 ThreadPoolExecutor 策略的坑
  3. 用邮箱实现多事件的单向同步
  4. 原始jdbc操作的分析
  5. JNDI(datasource)在tomcat,JBOSS下的spring+quartz配置
  6. jax-rs jax-ws_使用JAX-RS的HTTP缓存
  7. 随便唠叨下 最近的事情
  8. 双非,比赛经历对找算法类工作有帮助吗?
  9. laravel-excel文档翻译笔记
  10. bzoj 1488: [HNOI2009]图的同构
  11. iOS9新系统下App Store应用上传新指南
  12. 记小米公司的一次「测试开发工程师」面试
  13. python计算log函数
  14. 应用树莓派GPIO完成智能红绿灯系统
  15. 就业和工作?毕业生何去何从?
  16. android全屏保存壁纸,android设置全屏壁纸代码
  17. 多可文档管理迁移说明
  18. Oracle的授权方式
  19. YV12toI420 yuv420、NV12、YV12相互转换
  20. flask 调用python脚本_flaskpython脚本如何调用另一个flaskpython脚本

热门文章

  1. 怎么才可以使用 IPX 协议???
  2. 常用命令大全(网络命令+关机重启命令)
  3. Struts2拦截器-MethodFilterInterceptor
  4. 2008服务器系统显卡,Windows2008 R2 开启显卡硬件加速
  5. 超级外链工具-在线SEO超级外链群发工具免费
  6. 爬取天涯帖子(单个帖)
  7. 成理第二届信安大挑战web篇
  8. 万维考试系统python_万维题库管理系统单机版(万维考试管理软件)V201706 最新官方版...
  9. 堆空间释放后使用的异常
  10. adb shell dumpsys activity top