SpringBoot 整合 Kaptcha 实现 后台验证码问题
为什么要用验证码?
这个问题我以前也问过,当时我觉得验证码根本没啥用,就是一个摆设还浪费空间、时间,实则不然,小小验证码用途很大!它能够进行一次过滤,过滤掉那些机器人;用在注册可以防止机器注册是数据库爆炸,此外,也能够防止操作过于频繁。
为什么后端要实现验证码
验证码前端都可以实现为什么要用后端来?我某次再看帖子,这个帖子是这样子介绍的:前端验证码你可以理解为一层纱,后端验证码就是一栋墙,二者不在一个层次(可能不太准确)。学过一段爬虫,而且从事后端开发,当然知道可以直接调用接口实现功能,然而前端的验证码就真的成了虚设。。。
话不多说,开搞
1. 引入依赖
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
2. 基本配置
@Component
public class KaptchaConfig {
@Bean
public DefaultKaptcha getKaptcha() {
DefaultKaptcha kaptcha = new DefaultKaptcha();
Properties properties = new Properties();
//图片边框
properties.setProperty("kaptcha.border", "no");
//边框颜色
properties.setProperty("kaptcha.border.color", "105,179,90");
//字体颜色
properties.setProperty("kaptcha.textproducer.font.color", "red");
//设置图片的高
properties.setProperty("kaptcha.image.width", "110");
//设置字体大小
properties.setProperty("kaptcha.textproducer.font.size", "30");
// session key
properties.setProperty("kaptcha.session.key", "code");
// 验证码长度
properties.setProperty("kaptcha.textproducer.char.length", "4");
// 字体
properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
Config config = new Config(properties);
kaptcha.setConfig(config);
return kaptcha;
}
}
3. controller配置
这里我们采用的是将图片转为base64,然后返回给前端,让前端转成图片
问题解答:
为什么采用base64传图片?
- session 问题无法解决,使用session 我第一时间也想到了,但是前后端分离Session就显得略微鸡肋,前端拉着改了三天,后来发现每次请求的session都不一样,没法获取验证码。
- 响应头问题,我也想过使用响应头去解决这个问题,但是前端说node貌似没法获取响应头。。。。。。
在此处被迫使用base64位
配置如下:
@RestController
@RequestMapping("/Kaptcha")
public class KaptchaController {
@Autowired
DefaultKaptcha defaultKaptcha;
@Resource
RedisUtil redisUtil;
@RequestMapping("/defaultKaptcha")
public Map<String,Object> defaultKaptcha() throws Exception {
ByteArrayOutputStream out;
//生成UUID,作为条件,后续会讲到
String key=UUID.randomUUID().toString();
//生成验证码
String text = defaultKaptcha.createText();
System.out.println(text);
BufferedImage image = defaultKaptcha.createImage(text);
//这个是我们写的redis工具类,目的是为了更方便,这里表示存放到 redis里面,时间为 2小时
redisUtil.set("code:"+key,text,60*60*2);
out =new ByteArrayOutputStream();
ImageIO.write(image,"jpg",out);
Map<String,Object> map =new HashMap<>();
BASE64Encoder base64Encoder=new BASE64Encoder();
map.put("code-information",key);
map.put("img", "data:image/jpg;base64,"+base64Encoder.encode(out.toByteArray()).trim().replace("\n","").replace("\r",""));
return map;
}
}
实际controller层
@PostMapping("/login")
@ResponseBody
@ApiOperation(value = "进行登录", notes = "进行登录")
@ApiImplicitParams({
@ApiImplicitParam(name = "account", value = "账号", required = true),
@ApiImplicitParam(name = "password", value = "密码", required = true)
})
public Result login(String account, String password, String code, HttpServletRequest request) {
if (code == null || code.length() != 4) {
return Result.fail("请输入正确验证码");
}
//此处说明验证码的格式理论上是正确的
if (request.getHeader("code-information") == null ){
return Result.fail("请求失败");
}
//此处说明存在请求体
String head = request.getHeader("code-information");
if (!redisUtil.hasKey("code:"+head)){
//说明不存在
return Result.fail("验证码错误");
}
if (!redisUtil.get("code:"+head).equals(code)){
return Result.fail("验证码错误");
}
redisUtil.del("code:"+head);
return userService.login(account, password);
}
中间省略了其他的判断逻辑
细说:
这里大家可以看到有一个HttpServletRequest,用它去获取请求头, 获取的请求头位code-information
,心系的观众老爷们就发现了在生成验证码的时候也生成了UUID,最后存放的时候键就是code-information
,这里是通过前端设置请求头,然后将UUID放进去,然后通过redis进行验证。
大坑
JDK一定要使用JDK8 这个版本,因为JDK8 之后就不支持BASE64Encoder了,而是以 BaseUtil进行代替,据说是更加的安全
BASE64Encoder 的包
import sun.misc.BASE64Encoder;
末尾想问:
谁有更好的方法,希望给我讲讲,解决疑惑!