springboot圖片驗證碼功能模塊
前言:
大家好!我是小?。〗裉煳覀冇梦宸昼妬碛胹pringboot實現(xiàn)我們常用的圖形驗證碼功能模塊!
用戶登錄幾乎是一個線上系統(tǒng)必不可少且使用相對比較頻繁的一個模塊,為了防止惡意暴力嘗試,防止洪水攻擊、防止腳本自動提交等,驗證碼是一個較為便捷且行之有效的預(yù)防手段。
具體效果如下:

第一步:工具類
該工具類為生成驗證碼圖片的核心,直接拷貝到項目即可,無需做修改;可個性化的參數(shù)全部對外提供的API,比如 字體大小,背景顏色,干擾線數(shù)量,高寬等都可以根據(jù)自己的需求設(shè)置對應(yīng)參數(shù);
代碼幾乎每一行都加了詳細(xì)的注釋;如果遇上特殊的個性化需求,調(diào)整一下這個工具類即可實現(xiàn)。
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 {
// 默認(rèn)驗證碼字符集
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'};
// 默認(rèn)字符數(shù)量
private final Integer SIZE;
// 默認(rèn)干擾線數(shù)量
private final int LINES;
// 默認(rèn)寬度
private final int WIDTH;
// 默認(rèn)高度
private final int HEIGHT;
// 默認(rèn)字體大小
private final int FONT_SIZE;
// 默認(rèn)字體傾斜
private final boolean TILT;
private final Color BACKGROUND_COLOR;
/**
* 初始化基礎(chǔ)參數(shù)
*
* @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;
}
/**
* 實例化構(gòu)造器對象
*
* @return
*/
public static Builder newBuilder() {
return new Builder();
}
/**
* @return 生成隨機(jī)驗證碼及圖片
* Object[0]:驗證碼字符串;
* Object[1]:驗證碼圖片。
*/
public Object[] createImage() {
StringBuffer sb = new StringBuffer();
// 創(chuàng)建空白圖片
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
// 獲取圖片畫筆
Graphics2D graphic = image.createGraphics();
// 設(shè)置抗鋸齒
graphic.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 設(shè)置畫筆顏色
graphic.setColor(BACKGROUND_COLOR);
// 繪制矩形背景
graphic.fillRect(0, 0, WIDTH, HEIGHT);
// 畫隨機(jī)字符
Random ran = new Random();
//graphic.setBackground(Color.WHITE);
// 計算每個字符占的寬度,這里預(yù)留一個字符的位置用于左右邊距
int codeWidth = WIDTH / (SIZE + 1);
// 字符所處的y軸的坐標(biāo)
int y = HEIGHT * 3 / 4;
for (int i = 0; i < SIZE; i++) {
// 設(shè)置隨機(jī)顏色
graphic.setColor(getRandomColor());
// 初始化字體
Font font = new Font(null, Font.BOLD + Font.ITALIC, FONT_SIZE);
if (TILT) {
// 隨機(jī)一個傾斜的角度 -45到45度之間
int theta = ran.nextInt(45);
// 隨機(jī)一個傾斜方向 左或者右
theta = (ran.nextBoolean() == true) ? theta : -theta;
AffineTransform affineTransform = new AffineTransform();
affineTransform.rotate(Math.toRadians(theta), 0, 0);
font = font.deriveFont(affineTransform);
}
// 設(shè)置字體大小
graphic.setFont(font);
// 計算當(dāng)前字符繪制的X軸坐標(biāo)
int x = (i * codeWidth) + (codeWidth / 2);
// 取隨機(jī)字符索引
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++) {
// 設(shè)置隨機(jī)顏色
graphic.setColor(getRandomColor());
// 隨機(jī)畫線
graphic.drawLine(ran.nextInt(WIDTH), ran.nextInt(HEIGHT), ran.nextInt(WIDTH), ran.nextInt(HEIGHT));
}
// 返回驗證碼和圖片
return new Object[]{sb.toString(), image};
}
/**
* 隨機(jī)取色
*/
private Color getRandomColor() {
Random ran = new Random();
Color color = new Color(ran.nextInt(256), ran.nextInt(256), ran.nextInt(256));
return color;
}
/**
* 構(gòu)造器對象
*/
public static class Builder {
// 默認(rèn)字符數(shù)量
private int size = 4;
// 默認(rèn)干擾線數(shù)量
private int lines = 10;
// 默認(rèn)寬度
private int width = 80;
// 默認(rèn)高度
private int height = 35;
// 默認(rèn)字體大小
private int fontSize = 25;
// 默認(rèn)字體傾斜
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);
}
}
}
第二步:圖片生成:
使用默認(rèn)參數(shù):
//生成圖片驗證碼 Object[] verify = VerifyUtil.newBuilder().build().createImage();
自定義參數(shù)生成:
// 這個根據(jù)自己的需要設(shè)置對應(yīng)的參數(shù)來實現(xiàn)個性化
// 返回的數(shù)組第一個參數(shù)是生成的驗證碼,第二個參數(shù)是生成的圖片
Object[] objs = VerifyUtil.newBuilder()
.setWidth(120) //設(shè)置圖片的寬度
.setHeight(35) //設(shè)置圖片的高度
.setSize(6) //設(shè)置字符的個數(shù)
.setLines(10) //設(shè)置干擾線的條數(shù)
.setFontSize(25) //設(shè)置字體的大小
.setTilt(true) //設(shè)置是否需要傾斜
.setBackgroundColor(Color.WHITE) //設(shè)置驗證碼的背景顏色
.build() //構(gòu)建VerifyUtil項目
.createImage(); //生成圖片
整合到springboot項目中:
需要引入的maven依賴:
<!--redis相關(guān)配置-->
<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>
獲取相關(guān)的驗證碼:
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 {
/**
* 創(chuàng)建圖片驗證碼
* @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 {
@Resource
RedisServiceImpl redisUtil;
/**
* 生成圖片驗證碼
* @param response
* @param request
* @throws IOException
*/
@Override
public void createCode(HttpServletResponse response, HttpServletRequest request) throws IOException {
//獲取session
HttpSession session = request.getSession();
//獲得sessionId
String 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("清除成功");
}
//生成圖片驗證碼,用的默認(rèn)參數(shù)
Object[] verify = VerifyUtil.newBuilder().build().createImage();
//將驗證碼存入session
session.setAttribute("SESSION_VERIFY_CODE_" + id, verify[0]);
//打印驗證碼
System.out.println(verify[0]);
//將驗證碼存入redis
redisUtil.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);
}
@Override
public Result<String> checkCode(String verificationCode){
if (!redisUtil.hasKey(verificationCode)){
return new Result<>(false,"驗證碼錯誤");
}
redisUtil.del(verificationCode);
return R.success();
}
}
這里面還會用到redis相關(guān)的工具類,我就不列出來了,想要的話可以看我以前的文章工具類戳這里
controller層:
這里有用到@RequiredArgsConstructor, 就是簡單的注入而已, 如果想要詳細(xì)了解戳這里
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 這里主要就是多種驗證碼和登錄相關(guān)的東西
* @date 2022/4/11 21:46
*/
@RestController
@RequestMapping("/verify")
@RequiredArgsConstructor//這是在lombok工具給的注入方式,真帥
public class VerifyController {
private final VerifyService verifyService;
/**
* 獲取圖片驗證碼
*/
@LimitRequest(count = 5)//這個注解就是表示, 你在限制時間里(我們這里默認(rèn)是六秒鐘), 只能請求五次
@GetMapping("/getCode")
public void getCode(HttpServletResponse response, HttpServletRequest request) throws IOException {
verifyService.createCode(response, request);
}
@LimitRequest(count = 5)//這個注解就是表示, 你在限制時間里(我們這里默認(rèn)是六秒鐘), 只能請求五次
@GetMapping("/checkCode")
public Result<String> checkCode(String code){
return verifyService.checkCode(code);
}
}
這里為了不被一直無限制的訪問該服務(wù), 我們用了一個限制ip訪問次數(shù)的注解@LimitRequest
annotion包下的注解類:
package com.feng.annotation;
import java.lang.annotation.*;
/**
* @return null
* @author Ladidol
* @description 限制ip訪問次數(shù)注解
* @date 2022/4/11 22:15
*/
@Documented
@Target(ElementType.METHOD) // 說明該注解只能放在方法上面
@Retention(RetentionPolicy.RUNTIME)
public @interface LimitRequest {
long time() default 6000; // 限制時間 單位:毫秒
int count() default 3; // 允許請求的次數(shù)
}
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注解的方法都執(zhí)行切面方法
@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對象, 如果沒有則返回默認(rèn)值
// 第一個參數(shù)是key, 第二個參數(shù)是默認(rèn)值
ExpiringMap<String, Integer> uc = book.getOrDefault(request.getRequestURI(), ExpiringMap.builder().variableExpiration().build());
Integer uCount = uc.getOrDefault(request.getRemoteAddr(), 0);
if (uCount >= limitRequest.count()) { // 超過次數(shù),不執(zhí)行目標(biāo)方法
System.out.println("接口請求超過次數(shù)!");
throw new AppException("接口請求超過次數(shù)!");
} else if (uCount == 0) { // 第一次請求時,設(shè)置有效時間
//
uc.put(request.getRemoteAddr(), uCount + 1, ExpirationPolicy.CREATED, limitRequest.time(), TimeUnit.MILLISECONDS);
} else { // 未超過次數(shù), 記錄加一
uc.put(request.getRemoteAddr(), uCount + 1);
}
book.put(request.getRequestURI(), uc);
// result的值就是被攔截方法的返回值
Object result = pjp.proceed();
return result;
}
}
為了捕獲全局的異常拋出, 且符合restful規(guī)范我們加一個這個處理類:
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)
@ResponseBody
public Result error(Exception e) {
log.error(e.getMessage());
e.printStackTrace();
return R.fail(e.getMessage());
}
@ExceptionHandler(AppException.class)
@ResponseBody
public Result error(AppException e) {
log.error(e.getMessage());
e.printStackTrace();
return R.fail(e.getMessage());
}
}
application.yaml文件:
spring:
cache:
type:
redis
redis: #redis連接配置
host: 自己redis的ip地址
port: redis端口
password: 密碼
jedis:
pool:
max-active: 8
max-wait: -1ms
max-idle: 500
min-idle: 0
lettuce:
shutdown-timeout: 0ms
最終項目結(jié)構(gòu)如下:

先得到一個驗證碼:

驗證一下是否成功:
成功結(jié)果:

驗證失敗結(jié)果:

當(dāng)請求在規(guī)定時間內(nèi)的請求數(shù)超過規(guī)定的數(shù)量時或有報錯:

參考:
到此這篇關(guān)于springboot圖片驗證碼的文章就介紹到這了,更多相關(guān)springboot圖片驗證碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot+MyBatis實現(xiàn)登錄案例
前端時間在網(wǎng)上看到有朋友在學(xué)習(xí)springboot項目的搭建過程,今天就抽空給大家分享一個案例幫助大家學(xué)習(xí)SpringBoot+MyBatis實現(xiàn)登錄功能,具體實現(xiàn)代碼跟隨小編一起看看吧2021-06-06
spring cloud gateway 全局過濾器的實現(xiàn)
全局過濾器作用于所有的路由,不需要單獨配置,我們可以用它來實現(xiàn)很多統(tǒng)一化處理的業(yè)務(wù)需求,這篇文章主要介紹了spring cloud gateway 全局過濾器的實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-03-03
java 刪除數(shù)組元素與刪除重復(fù)數(shù)組元素的代碼
在java中刪除數(shù)組元素與過濾重復(fù)數(shù)組元素我們都會需要去遍歷數(shù)組然后根據(jù)我們設(shè)置的值或方法進(jìn)行去除數(shù)組2013-10-10
MongoDB中ObjectId的誤區(qū)及引起的一系列問題
這篇文章主要介紹了MongoDB中ObjectId的誤區(qū)及引起的一系列問題,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-12-12
ArrayList在for循環(huán)中使用remove方法移除元素方法介紹
這篇文章主要介紹了ArrayList在for循環(huán)中使用remove方法移除元素的內(nèi)容,介紹了具體代碼實現(xiàn),需要的朋友可以參考下。2017-09-09
SpringBoot使用FFmpeg實現(xiàn)M3U8切片轉(zhuǎn)碼播放
FFmpeg是一個開源跨平臺的多媒體處理工具套件,它支持音頻、視頻文件的編碼、解碼、流媒體傳輸以及轉(zhuǎn)換等多種操作,本文小編給大家介紹了SpringBoot使用FFmpeg實現(xiàn)M3U8切片轉(zhuǎn)碼播放的操作,需要的朋友可以參考下2024-08-08
SpringBoot應(yīng)用程序轉(zhuǎn)換成WAR文件詳解
其實一般使用SpringBoot使用打成jar包比較省事的,但也有很多童鞋是習(xí)慣使用WAR包的,下面這篇文章主要給大家介紹了關(guān)于SpringBoot轉(zhuǎn)換WAR的相關(guān)資料,需要的朋友可以參考下2022-11-11

