验证码
# 验证码概述
验证码通常用于登录、注册的表单中,防止恶意的重复请求。目前验证码大致的分类有两种:常规验证码
和滑块验证码
。
常规验证码
常规验证码一般就是以前那种需要输入对应验证码的
数字
、英文
等。滑块验证码
滑块验证码则极大的提高了用户的体验,用户只需要拖动鼠标就能够完成校验,目前很多校验都已经采用了滑块校验。
# 普通验证码实现
# 添加依赖
普通验证码这里采用的是easy-captcha
,相关依赖如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
# 配置验证码
由于验证码可能我们有时候需要关闭,或者需要去切换验证码的类型,所以验证码的相关配置我们采用配置文件的方式来实现。
@Component
@ConfigurationProperties(prefix = "login.captcha")
@Data
public class CaptchaProperties {
/**
* 是否启用验证码
*/
private Boolean enabled = true;
/**
* 验证码 key
*/
private String codeKey;
/**
* 验证码配置
*/
private LoginCodeEnum codeType;
/**
* 验证码有效期 分钟
*/
private Long expiration;
/**
* 验证码内容长度
*/
private int length;
/**
* 验证码宽度
*/
private int width;
/**
* 验证码高度
*/
private int height;
/**
* 验证码字体
*/
private String fontName;
/**
* 字体大小
*/
private int fontSize;
}
配置文件如下:
# 普通验证码
login:
captcha:
# 是否启用验证码
enabled: true
# 验证码 key
code-key: code-key-
# 验证码类型配置 查看 LoginCodeEnum 类
code-type: arithmetic
# 登录图形验证码有效时间/分钟
expiration: 2
# 验证码高度
width: 111
# 验证码宽度
height: 36
# 内容长度
length: 4
# 字体名称,为空则使用默认字体
font-name:
# 字体大小
font-size: 25
验证码枚举类:
public enum LoginCodeEnum {
/**
* 算数
*/
arithmetic,
/**
* 中文
*/
chinese,
/**
* 中文闪图
*/
chinese_gif,
/**
* 闪图
*/
gif,
/**
* 英文
*/
spec
}
到此验证码的前提工作我们就完成了,现在我们需要根据所配置的验证码类型,去生成对应类型的验证码,通过上面的配置文件,现在我们生成的验证码类型为计算型
。
# 生成验证码
创建一个CaptchaUtil
工具类来实现生成验证码相关操作。
@Component
public class CaptchaUtil {
@Autowired
private CaptchaProperties captchaProperties;
/**
* 获取验证码生产类
*
* @return /
*/
public Captcha getCaptcha() {
return switchCaptcha(captchaProperties);
}
/**
* 依据配置信息生产验证码
*
* @param captchaProperties 验证码配置信息
* @return /
*/
private Captcha switchCaptcha(CaptchaProperties captchaProperties) {
Captcha captcha;
synchronized (this) {
switch (captchaProperties.getCodeType()) {
case arithmetic:
// 算术类型 https://gitee.com/whvse/EasyCaptcha
captcha = new FixedArithmeticCaptcha(captchaProperties.getWidth(), captchaProperties.getHeight());
// 几位数运算,默认是两位
captcha.setLen(captchaProperties.getLength());
break;
case chinese:
captcha = new ChineseCaptcha(captchaProperties.getWidth(), captchaProperties.getHeight());
captcha.setLen(captchaProperties.getLength());
break;
case chinese_gif:
captcha = new ChineseGifCaptcha(captchaProperties.getWidth(), captchaProperties.getHeight());
captcha.setLen(captchaProperties.getLength());
break;
case gif:
captcha = new GifCaptcha(captchaProperties.getWidth(), captchaProperties.getHeight());
captcha.setLen(captchaProperties.getLength());
break;
case spec:
captcha = new SpecCaptcha(captchaProperties.getWidth(), captchaProperties.getHeight());
captcha.setLen(captchaProperties.getLength());
break;
default:
throw new BadConfigurationException("验证码配置信息错误!");
}
}
if(StrUtil.isNotBlank(captchaProperties.getFontName())){
captcha.setFont(new Font(captchaProperties.getFontName(), Font.PLAIN, captchaProperties.getFontSize()));
}
return captcha;
}
static class FixedArithmeticCaptcha extends ArithmeticCaptcha {
public FixedArithmeticCaptcha(int width, int height) {
super(width, height);
}
@Override
protected char[] alphas() {
// 生成随机数字和运算符
int n1 = num(1, 10), n2 = num(1, 10);
int opt = num(3);
// 计算结果
int res = new int[]{n1 + n2, n1 - n2, n1 * n2}[opt];
// 转换为字符运算符
char optChar = "+-x".charAt(opt);
this.setArithmeticString(String.format("%s%c%s=?", n1, optChar, n2));
this.chars = String.valueOf(res);
return chars.toCharArray();
}
}
}
这里最关键的就是switchCaptcha
方法,这个方法通过拿到CaptchaProperties
的验证码类型codeType
,来生成对应类型的验证码。
BadConfigurationException
参考如下:
public class BadConfigurationException extends RuntimeException {
public BadConfigurationException() {
super();
}
public BadConfigurationException(String message) {
super(message);
}
public BadConfigurationException(String message, Throwable cause) {
super(message, cause);
}
public BadConfigurationException(Throwable cause) {
super(cause);
}
protected BadConfigurationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
# 获取验证码
现在前端就需要通过请求,来获取验证码,这里为用户的体验,我们需要把验证码的value
存放到Redis中,校验的时候再从Redis中获取即可。
@ApiOperation("获取验证码")
@RequestMapping(value = "/code", method = RequestMethod.GET)
public Result getCode() {
if (!captchaProperties.getEnabled()) {
// 验证码信息
Map<String, Object> imgResult = new HashMap<String, Object>(2) {{
put("enabled", 0);
}};
return Result.success(imgResult);
}
// 获取运算的结果
Captcha captcha = captchaUtil.getCaptcha();
String uuid = captchaProperties.getCodeKey() + IdUtil.simpleUUID();
// 当验证码类型为 arithmetic时且长度 >= 2 时,captcha.text()的结果有几率为浮点型
String captchaValue = captcha.text();
if (captcha.getCharType() - 1 == LoginCodeEnum.arithmetic.ordinal() && captchaValue.contains(".")) {
captchaValue = captchaValue.split("\\.")[0];
}
// 保存
redisUtil.set(uuid, captchaValue, captchaProperties.getExpiration(), TimeUnit.MINUTES);
// 验证码信息
Map<String, Object> imgResult = new HashMap<String, Object>(2) {{
put("enabled", 1);
put("img", captcha.toBase64());
put("uuid", uuid);
}};
return Result.success(imgResult);
}
# 校验验证码
代码参考如下:
// 开启验证码
if(captchaProperties.getEnabled()) {
// 查询验证码
String code = (String) redisUtil.get(userDto.getUuid());
// 清除验证码
redisUtil.del(userDto.getUuid());
// 拿到是空,证明redis缓存已经过期
if (StrUtil.isBlank(code)) {
throw new BadRequestException("验证码不存在或已过期");
}
if (StrUtil.isBlank(userDto.getCode()) || !userDto.getCode().equalsIgnoreCase(code)) {
throw new BadRequestException("验证码错误");
}
}
BadRequestException
参考如下:
public class BadRequestException extends RuntimeException{
public BadRequestException(String msg){
super(msg);
}
}
UserDto
参考如下:
@Data
public class UserDto {
@NotBlank
private String username;
@NotBlank
private String password;
private String code;
private String uuid = "";
}
# 滑块验证码实现
滑块验证码采用AJ-Captcha (opens new window)
# 添加依赖
<dependency>
<groupId>com.anji-plus</groupId>
<artifactId>spring-boot-starter-captcha</artifactId>
<version>1.3.0</version>
</dependency>
# 配置文件
# 行为验证码
aj:
captcha:
aes-status: true
cache-type: local
font-size: 25
font-style: 1
history-data-clear-enable: false
interference-options: 1
req-check-minute-limit: 60
req-frequency-limit-enable: false
req-get-lock-limit: 5
req-get-lock-seconds: 360
req-get-minute-limit: 30
req-verify-minute-limit: 60
slip-offset: 5
type: default
water-mark: 旭日
jigsaw: classpath:images/jigsaw
pic-click: classpath:images/pic-click
# 相关接口
具体参数去参考官网文档。
# 前端实现
具体实现请参考官网文档。
# 参考
https://captcha.anji-plus.com/#/doc
https://el-admin.vip/
上次更新: 2024/06/29, 15:13:44