SpringBoot + Vue + Redis 实现验证码登录功能
前言
验证码在项目中使用到的较多,主要的目的是防止暴力破解和自动化脚本攻击。验证码需要人工识别图片中的内容,可以组织批量的自动化攻击。同时可以减轻数据库的访问压力,验证码不通过的情况下,不去进行查表操作。这篇文章大致说明一下具体的实现过程~
实现效果演示
这里的图片二维码是后端接口返回的base64(图片转base64),然后渲染到页面部分。用户点击这个图片可以生成新的验证码。验证码的有效时长是1分钟、超过有效期 ,验证失效。(有效时长可以通过改变验证码存入Redis的时长来调整)。
实现过程大致说明
1、后端pom文件引入Redis依赖,redis 连接 等下相关配置;
2、生成验证码保存到Redis中(同时设置过期时间);
3、将生成的图片验证码以base64格式返回给前端,渲染出来;
4、用户输入的验证码和Redis的比对;
后端接口实现(核心部分)
验证码生成部分
/*** 后台生成图形验证码 :有效* @param response* @param key*/@GetMapping(value = "/randomImage/{key}")public Result randomImage(HttpServletResponse response, @PathVariable("key") String key){logger.info("进入后台生成图形验证码方法 ...");JSONObject jsonObject = new JSONObject();try {//生成验证码String code = RandomUtil.randomString(BASE_CHECK_CODES,4);//存到redis中String lowerCaseCode = code.toLowerCase();// 加入密钥作为混淆,避免简单的拼接,被外部利用,用户自定义该密钥即可logger.info("RedisKey: {}", key);String keyPrefix = Md5Util.md5Encode(key + SIGNATURE_SECRET, "utf-8");String realKey = keyPrefix + lowerCaseCode;redisUtil.removeAll(keyPrefix);redisUtil.set(realKey, lowerCaseCode, 60);logger.info("获取验证码,Redis key = {},checkCode = {}", realKey, code);//返回前端String base64 = RandImageUtil.generate(code);jsonObject.put("base64", base64);return Result.ok().data(jsonObject);} catch (Exception e) {logger.error(e.getMessage(), e);return Result.error().code(ResultCode.GET_VERIFY_CODE_ERROR.getCode()).message(ResultCode.GET_VERIFY_CODE_ERROR.getMessage());}}
生成base64图片字符串
/*** 生成base64字符串* @param resultCode* @return* @throws IOException*/public static String generate(String resultCode) throws IOException {BufferedImage image = getImageBuffer(resultCode);ByteArrayOutputStream byteStream = new ByteArrayOutputStream();//写入流中ImageIO.write(image, IMG_FORMAT, byteStream);//转换成字节byte[] bytes = byteStream.toByteArray();//转换成base64串String base64 = Base64.getEncoder().encodeToString(bytes).trim();//删除 \r\nbase64 = base64.replaceAll("\n", "").replaceAll("\r", "");//写到指定位置//ImageIO.write(bufferedImage, "png", new File(""));return BASE64_PRE+base64;}
验证部分,将用户输入的验证码和Redis存储的比对。
@RequestMapping(value = "/Login/userLogin", method = RequestMethod.POST)public Result loginSystem(@RequestBody SysLoginModel sysLoginModel, HttpServletRequest request) {log.info("用户登录开始:{}", sysLoginModel);String captcha = sysLoginModel.getCaptcha();String checkKey = sysLoginModel.getCheckKey();if(captcha==null){return Result.error().code(ResultCode.VERIFY_CODE_ERROR.getCode()).message(ResultCode.VERIFY_CODE_ERROR.getMessage());}String lowerCaseCaptcha = captcha.toLowerCase();String realKey = Md5Util.md5Encode(checkKey + SIGNATURE_SECRET, "utf-8") + lowerCaseCaptcha;Object checkCode = redisUtil.get(realKey);log.info("Redis存储验证码: {} , 用户输入验证码: {}", checkCode , lowerCaseCaptcha);if(checkCode == null || !checkCode.equals(lowerCaseCaptcha)) {return Result.error().code(ResultCode.VERIFY_CODE_NOT_EXIST.getCode()).message(ResultCode.VERIFY_CODE_NOT_EXIST.getMessage());}}
前端接口实现 (核心部分)
页面实现部分、用的vue2
<el-form-item prop="captcha">
<div class="captcha-box"><el-inputv-model="form.captcha"placeholder="验证码"prefix-icon="el-icon-key"/><div class="captcha-img"><imgv-if="randCodeData.requestCodeSuccess"style="margin-top: 2px; max-width: initial":src="randCodeData.randCodeImage"@click="handleChangeCheckCode"/></div>
</div>
</el-form-item>
js部分实现
handleChangeCheckCode() {const _this = this;this.randCodeData.checkKey =new Date().getTime() + Math.random().toString(36).slice(-4); this.$axios.getRandomImage(_this.randCodeData.checkKey).then(function (resp) {if (resp.code == 200) {_this.randCodeData.randCodeImage = resp.data.base64;_this.randCodeData.requestCodeSuccess = true;} else {_this.$message.error(resp.message);}});},