验证码生成 EasyCaptcha
JavaWeb图形验证码,支持gif验证码,可用于基于的session的web项目和前后端分离的项目。
源码地址: https://gitee.com/jeesys/EasyCaptcha/
一、引入依赖
引入EasyCaptcha
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
二、二维码常用类型
- png类型
@Api("验证码图片获取")
@RestController
@RequestMapping(value = "/v1/captcha")
public class CaptchaController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@ResponseBody
@GetMapping
@ResponseResult
@ApiOperation("验证码图片获取")
public CaptchaVO captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5);
String verCode = specCaptcha.text().toLowerCase();
String key = UUID.randomUUID().toString();
// 存入redis并设置过期时间为30分钟
redisTemplate.opsForValue().set(key, verCode, 30, TimeUnit.MINUTES);
CaptchaVO captchaVO = new CaptchaVO();
captchaVO.setKey(key);
captchaVO.setImage(specCaptcha.toBase64());
return captchaVO;
}
三、创建Captcha注释
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Captcha {
/**
* 唯一标识
*/
String value() default "";
}
二、创建切面类
@Aspect
@Component
public class CaptchaAspect {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 此处的切点是注解的方式,也可以用包名的方式达到相同的效果
* '@Pointcut("execution(* com.wwj.springboot.service.impl.*.*(..))")'
*/
@Pointcut("@annotation(com.wayz.lbi.cloud.annotation.Captcha)")
public void captcha() {
}
/**
* 环绕增强,相当于MethodInterceptor
*/
@Around("captcha()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Captcha annotation = signature.getMethod().getAnnotation(Captcha.class);
String codevalue = annotation.value();
Object[] args = joinPoint.getArgs();
Object arg = args[0];
UserTO userTO = (UserTO) arg;
if (userTO != null && userTO.getPhone() != null) {
String phone = userTO.getPhone();
String isSmsLogin = userTO.getCode();
String frequencykey = codevalue + phone + ":frequency";
String timekey = codevalue + phone + ":time";
Integer integer = (Integer) redisTemplate.opsForValue().get(frequencykey); //用户登录失败,integer不为null
if (integer != null && integer >= 5) {
Object timeValue = redisTemplate.opsForValue().get(timekey);
throw new CloudException(CloudErrorCode.ACCOUNT_FREEZE, Collections.singletonMap(Global.ERROR_INFO,
String.format(CloudErrorCode.ACCOUNT_FREEZE.getMessage(), timeValue)));
}
// 密码当天24小时内填写失败次数大于3次,触发验证码机制
if ((integer != null && integer >= 3)||codevalue.equals("put")||!StringUtils.isEmpty(isSmsLogin)) {
String verKey = userTO.getVerKey();
String verCode = userTO.getVerCode();
if (StringUtils.isAnyEmpty(verKey, verCode)) {
throw new CloudException(CloudErrorCode.VERIFICATION_CODE_ERROR);
}
String redisCode = (String) redisTemplate.opsForValue().get(verKey);
if (StringUtils.isAnyEmpty(redisCode)) {
throw new CloudException(CloudErrorCode.VERIFICATION_CODE_ERROR);
}
//仅使用一次,在校验前删除验证码
redisTemplate.delete(verKey);
// 判断验证码
if (!StringUtils.equalsIgnoreCase(redisCode, verCode.trim())) {
throw new CloudException(CloudErrorCode.VERIFICATION_CODE_ERROR);
}
}
try {
return joinPoint.proceed();
} catch (Throwable throwable) {
needCaptcha(frequencykey, timekey);
throw throwable;
}
}
return joinPoint.proceed();
}
private void needCaptcha(String frequencykey, String timekey) {
Integer increment = (Integer) redisTemplate.opsForValue().get(frequencykey);
if (increment == null) {
//默认密码错误记录两小时
redisTemplate.opsForValue().set(frequencykey, 1, 24, TimeUnit.HOURS);
} else {
Long increment1 = redisTemplate.opsForValue().increment(frequencykey);
if (increment1 != null && increment1 == 5L) {
String format = LocalDateTime.now()
.plusHours(2).format(DateTimeFormatter.ofPattern("HH:mm"));
//超过五次则锁定两小时
redisTemplate.expire(frequencykey, 2, TimeUnit.HOURS);
redisTemplate.opsForValue().set(timekey, format);
}
}
}
}
用于用户登录失败的时候进行记录
四、创建验证码类
@Api("验证码图片获取")
@RestController
@RequestMapping(value = "/v1/captcha")
public class CaptchaController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@ResponseBody
@GetMapping
@ResponseResult
@ApiOperation("验证码图片获取")
public CaptchaVO captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5);
String verCode = specCaptcha.text().toLowerCase();
String key = UUID.randomUUID().toString();
// 存入redis并设置过期时间为30分钟
redisTemplate.opsForValue().set(key, verCode, 30, TimeUnit.MINUTES);
CaptchaVO captchaVO = new CaptchaVO();
captchaVO.setKey(key);
captchaVO.setImage(specCaptcha.toBase64());
return captchaVO;
}
@ApiOperation(value = "根据phone查询错误次数",
notes = "codevalue 表示不同的场景 登录+验证码校验为one , 注册为post ,重置密码为put;")
@ResponseResult
@GetMapping("/time")
public int getCaptchaTime(@RequestParam String phone, @RequestParam String codevalue) {
String frequencykey = codevalue + phone + ":frequency";
Integer integer = (Integer) redisTemplate.opsForValue().get(frequencykey);
return integer==null?0:integer;
}
}
和用户登录类
@Captcha("one")
@ApiOperation("登录+验证码校验")
@PostMapping(value = "/one")
public UserVO getByCodeOrPass(@RequestBody UserTO userTO) {
checkGetParam(userTO);
return userService.getByCodeOrPass(userTO);
}
@PostMapping
public boolean post(@RequestBody @Valid UserTO userTO) {
return userService.post(userTO);
}
@Captcha("put")
@PutMapping
public boolean put(@RequestBody UserTO userTO) {
checkPut(userTO);
return userService.put(userTO);
}
用户登录时先请求/time,再登录请求/one