【项目背景】

考虑登录时的验证安全,需要添加验证码验证,纯前端实现的验证码其实没有真正意义上做到安全验证的要求,简单一个网页爬虫就能获取到前端生成的验证码,所以应该由后台生成验证码,并由后台完成校验过程。

【实现思路】

登录页面初始化的时候,向后台请求返回base64格式的图片流,渲染到验证码控件上,前端用户输入验证码提交到后台进行对比校验。

【实现过程】

Spring Boot 后端代码

#1#-> 先创建一个验证码图片生成类 VerifyCodeUtils.java

package com.mhwz.utils;import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Random;public class VerifyCodeUtils{//使用到Algerian字体,系统里没有的话需要安装字体,字体只显示大写,去掉了1,0,i,o几个容易混淆的字符
public static final String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
private static Random random = new Random();/*** 使用系统默认字符源生成验证码** @param verifySize 验证码长度* @return*/
public static String generateVerifyCode(int verifySize) {return generateVerifyCode(verifySize, VERIFY_CODES);
}/*** 使用指定源生成验证码** @param verifySize 验证码长度* @param sources    验证码字符源* @return*/
public static String generateVerifyCode(int verifySize, String sources) {if (sources == null || sources.length() == 0) {sources = VERIFY_CODES;}int codesLen = sources.length();Random rand = new Random(System.currentTimeMillis());StringBuilder verifyCode = new StringBuilder(verifySize);for (int i = 0; i < verifySize; i++) {verifyCode.append(sources.charAt(rand.nextInt(codesLen - 1)));}return verifyCode.toString();
}/*** 生成随机验证码文件,并返回验证码值** @param w* @param h* @param outputFile* @param verifySize* @return* @throws IOException*/
public static String outputVerifyImage(int w, int h, File outputFile, int verifySize) throws IOException {String verifyCode = generateVerifyCode(verifySize);outputImage(w, h, outputFile, verifyCode);return verifyCode;
}/*** 输出随机验证码图片流,并返回验证码值** @param w* @param h* @param os* @param verifySize* @return* @throws IOException*/
public static String outputVerifyImage(int w, int h, OutputStream os, int verifySize) throws IOException {String verifyCode = generateVerifyCode(verifySize);outputImage(w, h, os, verifyCode);return verifyCode;
}/*** 生成指定验证码图像文件** @param w* @param h* @param outputFile* @param code* @throws IOException*/
public static void outputImage(int w, int h, File outputFile, String code) throws IOException {if (outputFile == null) {return;}File dir = outputFile.getParentFile();if (!dir.exists()) {dir.mkdirs();}try {outputFile.createNewFile();FileOutputStream fos = new FileOutputStream(outputFile);outputImage(w, h, fos, code);fos.close();} catch (IOException e) {throw e;}
}/*** 输出指定验证码图片流** @param w* @param h* @param os* @param code* @throws IOException*/
public static void outputImage(int w, int h, OutputStream os, String code) throws IOException {int verifySize = code.length();BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);Random rand = new Random();Graphics2D g2 = image.createGraphics();g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);Color[] colors = new Color[5];Color[] colorSpaces = new Color[]{Color.WHITE, Color.CYAN,Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,Color.PINK, Color.YELLOW};float[] fractions = new float[colors.length];for (int i = 0; i < colors.length; i++) {colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];fractions[i] = rand.nextFloat();}Arrays.sort(fractions);g2.setColor(Color.GRAY);// 设置边框色g2.fillRect(0, 0, w, h);Color c = getRandColor(200, 250);g2.setColor(c);// 设置背景色g2.fillRect(0, 2, w, h - 4);//绘制干扰线Random random = new Random();g2.setColor(getRandColor(160, 200));// 设置线条的颜色for (int i = 0; i < 20; i++) {int x = random.nextInt(w - 1);int y = random.nextInt(h - 1);int xl = random.nextInt(6) + 1;int yl = random.nextInt(12) + 1;g2.drawLine(x, y, x + xl + 40, y + yl + 20);}// 添加噪点float yawpRate = 0.05f;// 噪声率int area = (int) (yawpRate * w * h);for (int i = 0; i < area; i++) {int x = random.nextInt(w);int y = random.nextInt(h);int rgb = getRandomIntColor();image.setRGB(x, y, rgb);}shear(g2, w, h, c);// 使图片扭曲g2.setColor(getRandColor(100, 160));int fontSize = h - 4;Font font = new Font("Algerian", Font.ITALIC, fontSize);g2.setFont(font);char[] chars = code.toCharArray();for (int i = 0; i < verifySize; i++) {AffineTransform affine = new AffineTransform();affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize / 2, h / 2);g2.setTransform(affine);g2.drawChars(chars, i, 1, ((w - 10) / verifySize) * i + 5, h / 2 + fontSize / 2 - 10);}g2.dispose();ImageIO.write(image, "jpg", os);
}private static Color getRandColor(int fc, int bc) {if (fc > 255)fc = 255;if (bc > 255)bc = 255;int r = fc + random.nextInt(bc - fc);int g = fc + random.nextInt(bc - fc);int b = fc + random.nextInt(bc - fc);return new Color(r, g, b);
}private static int getRandomIntColor() {int[] rgb = getRandomRgb();int color = 0;for (int c : rgb) {color = color << 8;color = color | c;}return color;
}private static int[] getRandomRgb() {int[] rgb = new int[3];for (int i = 0; i < 3; i++) {rgb[i] = random.nextInt(255);}return rgb;
}private static void shear(Graphics g, int w1, int h1, Color color) {shearX(g, w1, h1, color);shearY(g, w1, h1, color);
}private static void shearX(Graphics g, int w1, int h1, Color color) {int period = random.nextInt(2);boolean borderGap = true;int frames = 1;int phase = random.nextInt(2);for (int i = 0; i < h1; i++) {double d = (double) (period >> 1)* Math.sin((double) i / (double) period+ (6.2831853071795862D * (double) phase)/ (double) frames);g.copyArea(0, i, w1, 1, (int) d, 0);if (borderGap) {g.setColor(color);g.drawLine((int) d, i, 0, i);g.drawLine((int) d + w1, i, w1, i);}}}private static void shearY(Graphics g, int w1, int h1, Color color) {int period = random.nextInt(40) + 10; // 50;boolean borderGap = true;int frames = 20;int phase = 7;for (int i = 0; i < w1; i++) {double d = (double) (period >> 1)* Math.sin((double) i / (double) period+ (6.2831853071795862D * (double) phase)/ (double) frames);g.copyArea(i, 0, 1, h1, 0, (int) d);if (borderGap) {g.setColor(color);g.drawLine(i, (int) d, i, 0);g.drawLine(i, (int) d + h1, i, h1);}}}

}

#2#-> Controller类 Controller.java

(1)新增验证码返回接口 /getVerifyCodeImage

// 生成验证码
@GetMapping("/getVerifyCodeImage")
public String getImageCode(HttpServletRequest request) throws IOException{
// 1. 使用工具类生成验证码
String code = VertifyCodeUtils.generateVerifyCode(4);
validate_code = code; // 存放生成的验证码
// 2. 将验证码放入ServletContext作用域
request.getServletContext().setAttribute("code", code);
// 3. 将图片转换成base64格式
// 字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
//将得到的验证码,使用工具类生成验证码图片,并放入到字节数组缓存区
VertifyCodeUtils.outputImage(220,60,byteArrayOutputStream,code);
//使用spring提供的工具类,将字节缓存数组中的验证码图片流转换成Base64的形式
//并返回给浏览器
return "data:image/png;base64," + Base64Utils.encodeToString(byteArrayOutputStream.toByteArray());

}
(2)在原本的登录接口/login中添加验证码校验过程

 // 验证成功if(verifyCode.equalsIgnoreCase(validate_code)){Page page = new Page();page.setReason(user);JSONObject userSession = (JSONObject) userService.login(acc,pwd);if(userSession == null){return new Result().fail("ACC OR PWD FAIL");}if(userSession.getBoolean("status") != null && !userSession.getBoolean("status")){throw new MyException("用户已被禁止启用").code(MyStatusCode.NOT_ENABLED);}session.setAttribute("USER",userSession.toJavaObject(UserSession.class));return new Result().success(userSession,1);} else { // 验证失败return new Result().fail("验证码错误");}

完整的Controller.java ​​​​​​

package com.controller;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.vone.mms.config.MyStatusCode;
import com.vone.mms.entity.MyException;
import com.vone.mms.entity.Result;
import com.vone.mms.entity.UserSession;
import com.vone.mms.entity.Page;
import com.vone.mms.entity.dao.MenuView;
import com.vone.mms.entity.dao.User;
import com.vone.mms.entity.web.VueRouter;
import com.vone.mms.util.VerifyCodeUtils;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.support.SessionStatus;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;@RestController
@RequestMapping
public class Controller {private String validate_code = null;@Autowired
protected UserService userService;@RequestMapping("login")
String login(HttpSession session , @RequestBody Map<String, String> user) throws MyException {String acc = user.get("acc");String pwd = user.get("pwd");String verifyCode = user.get("verifyCode");// 验证成功if(verifyCode.equalsIgnoreCase(validate_code)){Page page = new Page();page.setReason(user);JSONObject userSession = (JSONObject) userService.login(acc,pwd);if(userSession == null){return new Result().fail("ACC OR PWD FAIL");}if(userSession.getBoolean("status") != null && !userSession.getBoolean("status")){throw new MyException("用户已被禁止启用").code(MyStatusCode.NOT_ENABLED);}session.setAttribute("USER",userSession.toJavaObject(UserSession.class));return new Result().success(userSession,1);} else { // 验证失败return new Result().fail("验证码错误");}
}// 生成验证码
@GetMapping("/getVerifyCodeImage")
public String getImageCode(HttpServletRequest request) throws IOException {// 1. 使用工具类生成验证码String code = VerifyCodeUtils.generateVerifyCode(4);validate_code = code; // 存放生成的验证码// 2. 将验证码放入ServletContext作用域request.getServletContext().setAttribute("code", code);// 3. 将图片转换成base64格式// 字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();//将得到的验证码,使用工具类生成验证码图片,并放入到字节数组缓存区VerifyCodeUtils.outputImage(220,60,byteArrayOutputStream,code);//使用spring提供的工具类,将字节缓存数组中的验证码图片流转换成Base64的形式//并返回给浏览器return "data:image/png;base64," + Base64Utils.encodeToString(byteArrayOutputStream.toByteArray());}
}

Vue 前端代码

#1#-> 渲染验证码控件

<template><div id="app" :class="baseClasses" @keydown.enter="full_KeyD_Enter()"><div class="login-body"><div class="login-card px-boxShadow"><div class="login-card-line login-card-line-head"><h1 v-if="langType != 'zh-cn'">{{$t('yonghu')}} {{$t('denglu')}}</h1><h1 v-else>{{$t('yonghu')}}{{$t('denglu')}}</h1></div><div class="login-card-line login-card-line-acc"><el-input v-model="acc" placeholder="账号" maxlength="24" prefix-icon="el-icon-user" clearable></el-input></div><div class="login-card-line"><el-input v-model="pwd" placeholder="密码" show-password prefix-icon="el-icon-lock" clearable></el-input></div><!-- 验证码 --><div class="login-card-line"><el-input v-model="verifyCode" placeholder="验证码" prefix-icon="el-icon-key"><template slot="append"><el-image style="width: 100px" :src="code_url" @click="getVerifyCode" title="看不清?点击切换"></el-image></template></el-input></div><div class="login-card-line"><!-- <div>{{$t('gongsidaima')}}</div> --><!-- <input v-model="code" maxlength="6" :placeholder="$t('gongsidaima')" :class="['company-code-input',code==''? 'no-code':'got-code']" /> --><div style="float: right;display: flex;line-height: 29px;"><div style="margin-right: 10px;color: white;">{{$t('jizhumima')}}</div><el-switch style="margin-top: 4px;" v-model="remember" active-color="#13ce66"></el-switch></div></div><div class="login-card-line remember" v-if="false"></div><div class="login-lang-change" v-if="false"><div v-if="$i18n.locale == 'ZH-CN'" class="bechoice" @click="languageChange('ZH-CN')">中文</div><div v-if="$i18n.locale != 'ZH-CN'" @click="languageChange('ZH-CN')">中文</div><div v-if="$i18n.locale == 'ZH-CN'" @click="languageChange('EN')">English</div><div v-if="$i18n.locale != 'ZH-CN'" class="bechoice" @click="languageChange('EN')">English</div></div><div class="login-card-line login-card-line-foot"><button @click="loginCheck()" class="pointer">{{“登录”}}</button></div></div>
</div>
</div>
</template>

#2#-> data里添加对应的code_url字段

<script>export default {name: 'Login',data () {return {// 验证码图片URLcode_url: ''}}}
</script>

#3#-> methods添加获取验证码的请求代码

// 获取验证码async getVerifyCode () {var that = this;var axios = that.axios;axios({method: 'get',url: '/getVerifyCodeImage',df: false}).then((response) => {if (!response) {return}var data = response.data;that.code_url = data;}).catch((error) => {that.errorhanding(error)});},

#4#-> 初始化加载验证码

created () {
this.getVerifyCode() // 页面初始化从后端加载验证码
}
【实现效果】

【参考】

SpringBoot+Vue前后端分离Demo(一):验证码获取及显示
https://blog.csdn.net/CSRAWD/article/details/113988243

SPRINGBOOT+VUE实现请求后台获取BASE64编码的图片验证码并使用REDIS缓存实现2分钟内有效
https://www.freesion.com/article/20121255549/

前后端分离情况下的图片验证码验证问题
https://blog.csdn.net/jiahao791869610/article/details/79175268

前端+后端:验证码实现
https://www.cnblogs.com/makexu/articles/5314314.html

【sprintboot+vue】前后端分离模式下的登录验证码验证相关推荐

  1. 前后端分离模式下的权限设计方案

    前后端分离模式下,所有的交互场景都变成了数据,传统业务系统中的权限控制方案在前端已经不再适用,因此引发了我对权限的重新思考与设计. 权限控制到底控制的是什么? 在理解权限控制之前,需要明白两个概念:资 ...

  2. SpringBoot+Vue前后端分离实战(用户注册登录)

    文章目录 前言 注册 前端部分逻辑 发送请求 后端处理 登录 前端获取token 前端token状态管理 后端处理 用户登录 生成token 拦截器设置 总结 前言 昨天抽空终于把后端架起来了,准备开 ...

  3. 若依前后端分离版怎样去掉登录验证码

    场景 若依前后端分离版手把手教你本地搭建环境并运行项目: 若依前后端分离版手把手教你本地搭建环境并运行项目_BADAO_LIUMANG_QIZHI的博客-CSDN博客_若依前后端分离版本的配置 上面在 ...

  4. 前后端分离模式下前端与后端数据交互

    下面举的例子就是使用jQuery Ajax和Python Flask进行前后端交互时,前端提交表单数据到后端,后端返回JSON数据给前端. 前端GET提交表单数据: # GET请求var data = ...

  5. 搭建spring-boot+vue前后端分离框架并实现登录功能

    一.环境.工具 jdk1.8 maven spring-boot idea VSVode vue 二.搭建后台spring-boot框架 步骤: 1.new- project选择Spring Init ...

  6. Laravel+Vue前后端分离项目(四)邮箱验证与滑动验证

    流程图 在做业务逻辑前,我们需要做一个流程图,确保用户所走的业务逻辑不会出现死循环或者陷入死胡同. 效果图        步骤 1.前端页面 src/views/login.vue <templ ...

  7. 前后端分离开发下的权限管控 :SpringSecurity 框架

    首先在了解前后端分离模式下使用SpringSecurity框架之前,我们需要先了解token和jwt(Json Web Token)技术 token 和 session 的区别? 由于http协议是无 ...

  8. SpringBoot + Vue 前后端分离(用户信息更新头像上传Markdown图片上传)

    文章目录 前言 用户信息更新 前端发送 后端接口 修改用户头像 前端 前端图片显示 图片上传 完整 代码 后端代码 图片存储 图片上传工具类 图片工具类的配置 工具类实现 效果 Markdown 图片 ...

  9. 大二期末作孽(SpringBoot+Vue前后端分离博客社区(重构White Hole))

    文章目录 前言 目录 效果演示 前言 由于时间关系,完成度确实不高,而且不签只是完成了客户端,当然目前这样也是已经可以正常使用了,当然有点勉强.不过后续还是会不断的去更新维护的,不过大体的架构是这样的 ...

最新文章

  1. 对于广泛依赖外部资源的应用程序,请考虑在多处理器计算机上启用网络园艺
  2. 0 到 1 的过往,1 到 10 的未来 | 智源两周年,开启新篇章
  3. BC26通过MQTT协议连接ONENET,AT流程
  4. python with语句与contextlib
  5. UOJ #514 [UR #19]通用测评号 (容斥原理、DP)
  6. php网站自动变暗,如何使用JS弹出DIV并使整个页面背景变暗
  7. mac系统快捷键大全详细介绍
  8. PAT真题乙类1006 换个格式输出整数
  9. 【Python】Python库之图形艺术
  10. r语言plot函数设置y轴的范围及刻度_R语言之简单绘图
  11. jquery自定义banner图滚动插件---(解决最后一张图片倒回第一张图片的bug)
  12. My97DatePicker 组件使用方法---My97DatePicker
  13. 深度学习之江湖~那些大神们
  14. window 上 shell 连接工具
  15. kafka 下载安装
  16. 希尔排序、快速排序的每一趟
  17. Python3.6支付宝账单爬虫
  18. 前端vue几款模板介绍
  19. 211大学中哪几所计算机专业好,北京哪些211大学计算机专业比较好考研
  20. 介绍几个ipad的使用技巧

热门文章

  1. [资源]汇集最有用的PHP资源
  2. 杰夫贝佐斯 西装_杰夫·贝佐斯(Jeff Bezos)为长期成功提供建议
  3. C++标准库和模板库
  4. 科目二电子路训练笔记
  5. 在外包干了三年,我废了..… 不吹不黑!
  6. abb机器人指令手册_ABB机器人码垛2020
  7. android4.4.2 boot,nexus4 4.4.2完美root教程
  8. 迅为iTOP-2K1000开发板龙芯中科国产64位Loognix系统工业核心主板
  9. js获取域名:location.origin
  10. ssh mysql 电子商城_电子商城系统的设计与实现(SSH,MySQL)(含录像)