一、背景
在實現(xiàn)登錄功能時,為了防止特定的程序暴力破解,一般為了安全都會在用戶登錄時增加otp動態(tài)驗證碼錄。otp驗證碼 otp全稱叫One-time Password,也稱動態(tài)口令,是指計算機系統(tǒng)或其他數(shù)字設(shè)備上只能使用一次的密碼,有效期為只有一次登錄會話或很短。
常見驗證碼分為圖片驗證碼和短信驗證碼,還有滑動窗口模塊和選中指定物體驗證方式。下面通過Java來實現(xiàn)圖片驗證碼示例,效果如下:

二、實現(xiàn)步驟
1、maven中加入依賴
pom.xml引入依賴:
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
2、CaptchaController.java
/**
* 驗證碼
*/
@GetMapping("/captcha/digit")
@ApiOperation(value = "獲取數(shù)字驗證碼", notes = "獲取數(shù)字驗證碼", tags = "驗證碼相關(guān)")
@ApiImplicitParams({
@ApiImplicitParam(name = "uuid", value = "uuid", required = true, paramType = "query")
})
@PassToken
public void captcha(HttpServletResponse response, String uuid) throws IOException {
response.setHeader("Cache-Control", "no-store, no-cache");
response.setContentType("image/jpeg");
log.info("獲取驗證碼,uuid:{}", uuid);
//獲取圖片驗證碼
BufferedImage image = captchaService.getCaptcha(uuid);
log.info("獲取驗證碼,uuid:{},return:{}", uuid, JSON.toJSONString(image));
ServletOutputStream out = response.getOutputStream();
ImageIO.write(image, "jpg", out);
IOUtils.closeQuietly(out);
}
@GetMapping("/captcha/graphics")
@ApiOperation(value = "獲取圖形驗證碼", notes = "獲取圖形驗證碼", tags = "驗證碼相關(guān)")
@ApiImplicitParams({
@ApiImplicitParam(name = "uuid", value = "uuid", required = true, paramType = "query"),
@ApiImplicitParam(name = "type", value = "類型 png:png gif:gif cn:中文 cngif:中文gif arithmeti:算術(shù)", required = false, paramType = "query")
})
public void captcha(HttpServletRequest request, HttpServletResponse response,
@RequestParam String uuid,
@RequestParam(defaultValue = "arithmeti", required = false) String type) throws Exception {
// 設(shè)置請求頭為輸出圖片類型
response.setContentType("image/gif");
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
Captcha captcha = null;
switch (type) {
case "png":
captcha = new SpecCaptcha(130, 48);
break;
case "gif":
// gif類型
captcha = new GifCaptcha(130, 48);
break;
case "cn":
// 中文類型
captcha = new ChineseCaptcha(130, 48, 5, new Font("楷體", Font.PLAIN, 28));
break;
case "cngif":
// 中文gif類型
captcha = new ChineseGifCaptcha(130, 48, 5, new Font("楷體", Font.PLAIN, 28));
break;
case "arithmeti":
// 算術(shù)類型
ArithmeticCaptcha arithmeticCaptcha = new ArithmeticCaptcha(130, 48);
arithmeticCaptcha.setLen(3); // 幾位數(shù)運算,默認(rèn)是兩位
arithmeticCaptcha.getArithmeticString(); // 獲取運算的公式:3+2=?
arithmeticCaptcha.text(); // 獲取運算的結(jié)果:5
captcha = arithmeticCaptcha;
break;
default:
new SpecCaptcha(130, 48);
break;
}
log.info("驗證碼:{}", captcha.text());
// 設(shè)置字體
// captcha.setFont(new Font("Verdana", Font.PLAIN, 32)); // 有默認(rèn)字體,可以不用設(shè)置
// 設(shè)置類型,純數(shù)字、純字母、字母數(shù)字混合
captcha.setCharType(Captcha.TYPE_DEFAULT);
//緩存驗證碼
redisService.set(AuthKeys.AUTH_CAPTCHA, uuid, captcha.text().toLowerCase());
// 輸出圖片流
captcha.out(response.getOutputStream());
}
}
3、生成驗證碼配置
/**
* 生成驗證碼配置
*
*/
@Configuration
public class KaptchaConfig {
@Bean
public DefaultKaptcha producer() {
Properties properties = new Properties();
//圖片邊框
properties.setProperty("kaptcha.border", "no");
//文本集合,驗證碼值從此集合中獲取
properties.setProperty("kaptcha.textproducer.char.string", "ABCDEGHJKLMNRSTUWXY23456789");
//字體顏色
properties.setProperty("kaptcha.textproducer.font.color", "0,84,144");
//干擾顏色
properties.setProperty("kaptcha.noise.color", "0,84,144");
//字體大小
properties.setProperty("kaptcha.textproducer.font.size", "30");
//背景顏色漸變,開始顏色
properties.setProperty("kaptcha.background.clear.from", "247,255,234");
//背景顏色漸變,結(jié)束顏色
properties.setProperty("kaptcha.background.clear.to", "247,255,234");
//圖片寬
properties.setProperty("kaptcha.image.width", "125");
//圖片高
properties.setProperty("kaptcha.image.height", "35");
properties.setProperty("kaptcha.session.key", "code");
//驗證碼長度
properties.setProperty("kaptcha.textproducer.char.length", "4");
//字體
properties.setProperty("kaptcha.textproducer.font.names", "Arial,Courier,cmr10,宋體,楷體,微軟雅黑");
properties.put("kaptcha.textproducer.char.space", "5");
Config config = new Config(properties);
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
4、CaptchaService.java接口
public interface CaptchaService {
boolean validate(String uuid, String code);
BufferedImage getCaptcha(String uuid);
}
5、CaptchaServiceImpl.java實現(xiàn)類
@Service
@Slf4j
public class CaptchaServiceImpl implements CaptchaService {
@Autowired
private Producer producer;
@Autowired
private RedisService redisService;
@Value("${default-captcha}")
private String defaultCaptcha;
/**
* 生成并緩存驗證碼,返給前端圖片
*/
@Override
public BufferedImage getCaptcha(String uuid) {
if (StringUtils.isEmpty(uuid)) {
throw new GlobalException(BasicCodeMsg.PARAMETER_ERROR.setMsg("uuid不能為空"));
}
//生成文字驗證碼
String code = producer.createText();
log.info("uuid:{},驗證碼:{}",uuid,code);
//緩存驗證碼
redisService.set(AuthKeys.AUTH_CAPTCHA, uuid, code);
return producer.createImage(code);
}
}
/**
* 校驗驗證碼
*/
@Override
public boolean validate(String uuid, String code) {
//測試環(huán)境123456通過驗證(可不加)
if (EnvEnum.dev.name().equals(env) && code.equals(defaultCaptcha)) {
return true;
}
String cacheCode = redisService.get(AuthKeys.AUTH_CAPTCHA, uuid, String.class);
if (StringUtils.isEmpty(cacheCode)) {
return false;
}
//刪除緩存驗證碼
redisService.delete(AuthKeys.AUTH_CAPTCHA, uuid);
if (cacheCode.equalsIgnoreCase(code)) {
return true;
}
return false;
}
6、增加驗證碼校驗
在登錄授權(quán)驗證的地方添加驗證碼相關(guān)校驗,也就是原來校驗用戶名密碼的地方增加。
if ("captcha".equals(type)) {
LoginVo loginVo = LoginVo.builder().captcha(captcha)
.loginName(username)
.uuid(uuid).build();
boolean result = captchaService.validate(uuid, captcha);
if (!result) {
throw new OAuth2Exception("驗證碼不正確");
}
return;
涉及文件
