我们在上篇文章中:SpringBoot + Apache Shrio + JWT +Redis 实现权限认证,讲述如何基于用户名和密码实现权限认证功能,我们在此基础上进行功能完善,在用户登入时除了输入用户名和密码,还需要添加验证码字段进行验证。下面开始梳理验证码功能点
kaptcha简介
Kaptcha 是一个可高度配置的实用验证码生成工具,可自由配置的选项如:
- 验证码的字体
- 验证码字体的大小
- 验证码字体的字体颜色
- 验证码内容的范围(数字,字母,中文汉字!)
- 验证码图片的大小,边框,边框粗细,边框颜色
- 验证码的干扰线
- 验证码的样式(鱼眼样式、3D、普通模糊、...)
Kaptcha 配置参数表
Constant | 描述 | 默认值 |
kaptcha.border | 图片边框,合法值:yes , no | yes |
kaptcha.border.color | 边框颜色,合法值: r,g,b (and optional alpha) 或者 white,black,blue. | black |
kaptcha.image.width | 图片宽 | 200 |
kaptcha.image.height | 图片高 | 50 |
kaptcha.producer.impl | 图片实现类 | com.google.code.kaptcha.impl.DefaultKaptcha |
kaptcha.textproducer.impl | 文本实现类 | com.google.code.kaptcha.text.impl.DefaultTextCreator |
kaptcha.textproducer.char.string | 文本集合,验证码值从此集合中获取 | abcde2345678gfynmnpwx |
kaptcha.textproducer.char.length | 验证码长度 | 5 |
kaptcha.textproducer.font.names | 字体 | Arial, Courier |
kaptcha.textproducer.font.size | 字体大小 | 40px. |
kaptcha.textproducer.font.color | 字体颜色,合法值: r,g,b 或者 white,black,blue. | black |
kaptcha.textproducer.char.space | 文字间隔 | 2 |
kaptcha.noise.impl | 干扰实现类 | com.google.code.kaptcha.impl.DefaultNoise |
kaptcha.noise.color | 干扰 颜色,合法值: r,g,b 或者 white,black,blue. | black |
kaptcha.obscurificator.impl | 图片样式:<br />水纹 com.google.code.kaptcha.impl.WaterRipple <br /> 鱼眼 com.google.code.kaptcha.impl.FishEyeGimpy <br /> 阴影 com.google.code.kaptcha.impl.ShadowGimpy | com.google.code.kaptcha.impl.WaterRipple |
kaptcha.background.impl | 背景实现类 | com.google.code.kaptcha.impl.DefaultBackground |
kaptcha.background.clear.from | 背景颜色渐变,开始颜色 | light grey |
kaptcha.background.clear.to | 背景颜色渐变, 结束颜色 | white |
kaptcha.word.impl | 文字渲染器 | com.google.code.kaptcha.text.impl.DefaultWordRenderer |
kaptcha.session.key | session key | KAPTCHA_SESSION_KEY |
kaptcha.session.date | session date | KAPTCHA_SESSION_DATE |
SpringBoot 整合Kaptcha
在项目中引入kaptcha非常简单,我们只需要在pom.xml文件中引入 kaptcha就可以了
<!-- 依赖kaptcha图形生成器 -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
自定义kaptcha 工具类:
package com.zzg.util;
import java.util.Properties;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
public class KaptchaUtil {
private static KaptchaUtil instance;
private KaptchaUtil() {
};
public static KaptchaUtil getInstance() {
if (instance == null) {
instance = new KaptchaUtil();
}
return instance;
}
/**
* 生成DefaultKaptcha 默认配置
* @return
*/
public DefaultKaptcha produce() {
Properties properties = new Properties();
properties.put("kaptcha.border", "no");
properties.put("kaptcha.border.color", "105,179,90");
properties.put("kaptcha.textproducer.font.color", "blue");
properties.put("kaptcha.image.width", "100");
properties.put("kaptcha.image.height", "50");
properties.put("kaptcha.textproducer.font.size", "27");
properties.put("kaptcha.session.key", "code");
properties.put("kaptcha.textproducer.char.length", "4");
properties.put("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
properties.put("kaptcha.textproducer.char.string", "0123456789ABCEFGHIJKLMNOPQRSTUVWXYZ");
properties.put("kaptcha.obscurificator.impl", "com.google.code.kaptcha.impl.WaterRipple");
properties.put("kaptcha.noise.color", "black");
properties.put("kaptcha.noise.impl", "com.google.code.kaptcha.impl.DefaultNoise");
properties.put("kaptcha.background.clear.from", "185,56,213");
properties.put("kaptcha.background.clear.to", "white");
properties.put("kaptcha.textproducer.char.space", "3");
Config config = new Config(properties);
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
SpringBoot 添加验证码Controller
package com.zzg.controller;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.zzg.redis.RedisUtils;
import com.zzg.util.KaptchaUtil;
@RestController
public class KaptchaController {
public static final String VRIFY_CODE = "VRIFYCODE";
@GetMapping("/sys/kaptcha")
public void defaultKaptcha(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws Exception {
byte[] captchaChallengeAsJpeg = null;
ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
try {
// 代码方式创建:DefaultKaptcha
KaptchaUtil single = KaptchaUtil.getInstance();
DefaultKaptcha defaultKaptcha = single.produce();
// 生产验证码字符串并保存到session中
String createText = defaultKaptcha.createText();
// 基于session 方式
//httpServletRequest.getSession().setAttribute(VRIFY_CODE, createText);
// 基于redis 方式
RedisUtils.hPut(VRIFY_CODE, createText, createText);
// 使用生产的验证码字符串返回一个BufferedImage对象并转为byte写入到byte数组中
BufferedImage challenge = defaultKaptcha.createImage(createText);
ImageIO.write(challenge, "jpg", jpegOutputStream);
} catch (IllegalArgumentException e) {
httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// 定义response输出类型为image/jpeg类型,使用response输出流输出图片的byte数组
captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
httpServletResponse.setHeader("Cache-Control", "no-store");
httpServletResponse.setHeader("Pragma", "no-cache");
httpServletResponse.setDateHeader("Expires", 0);
httpServletResponse.setContentType("image/jpeg");
ServletOutputStream responseOutputStream = httpServletResponse.getOutputStream();
responseOutputStream.write(captchaChallengeAsJpeg);
responseOutputStream.flush();
responseOutputStream.close();
}
}
注意:存储验证码的方式是采用redis 方式存储,是基于前后端分离模式决定。一般采取的模式是session 方式存储
ShiroConfig 配置对象开发验证码权限
在shiroConfig中对 获取验证码的数据接口开放权限
登入方法添加验证码验证
在LoginController.login(@RequestParam("username")String username, @RequestParam("password")String password, @RequestParam("captcha")String captcha) 中添加验证码请求参数
/**
* 登录
*/
@PostMapping("/sys/login")
@ResponseBody
public Map<String, Object> login(@RequestParam("username")String username, @RequestParam("password")String password, @RequestParam("captcha")String captcha) {
Map<String, Object> result = new HashMap<>();
//校验验证码
//基于session的验证码
// String sessionCaptcha = (String) SecurityUtils.getSubject().getSession().getAttribute(VRIFY_CODE);
//基于redis的验证码
String redisCaptcha = (String) RedisUtils.hGet(VRIFY_CODE, captcha);
if (StringUtils.isEmpty(redisCaptcha)) {
result.put("status", 400);
result.put("msg", "验证码有误");
return result;
}
//用户信息
User user = userRepository.findByUsername(username);
//账号不存在、密码错误
if (user == null || !user.getPassword().equals(password)) {
result.put("status", 400);
result.put("msg", "账号或密码有误");
} else {
//生成token,并保存到redis
String token = JWTUtil.createToken(username);
RedisUtils.set(username,token);
RedisUtils.expire(username, EXPIRE_TIME);
// user->token 关系保存到mysql 中
TokenRelation relation = tokenRelationRepository.findByUsername(username);
if(relation == null){
relation = new TokenRelation();
relation.setUsername(username);
relation.setToken(token);
} else {
relation.setToken(token);
}
tokenRelationRepository.save(relation);
result.put("token", token);
result.put("status", 200);
result.put("msg", "登陆成功");
// 移除验证码
RedisUtils.hDelete(VRIFY_CODE, captcha);
}
return result;
}
效果演示: