欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 时评 > 在springboot加vue项目中加入图形验证码

在springboot加vue项目中加入图形验证码

2025/2/21 3:43:31 来源:https://blog.csdn.net/weixin_55347379/article/details/145642892  浏览:    关键词:在springboot加vue项目中加入图形验证码

后端

首先先要创建一个CaptchaController的类,可以在下面的代码中看到

在getCaptcha的方法里面写好了生成随机的4位小写字母或数字的验证码,然后通过BufferedImage类变为图片,顺便加上了干扰线。之后把图片转为Base64编码方便传给前端

为了安全我写了encrypt方法把4位验证码加密了一下,和图片放在了Mapli传给了前端,后面的verifyCaptcha是对前端输入的内容和我们生成的验证码进行比较,并返回状态码。

package cn.kmbeast.controller;import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;@RestController
@RequestMapping("/captcha")
public class CaptchaController {private static final String ALGORITHM = "AES";private static final String SECRET_KEY = "1234567890123456"; // 16字节的密钥@GetMapping("/get")public Map<String, Object> getCaptcha(HttpServletResponse response) throws Exception {System.out.println("验证码已生成");response.setContentType("image/png");response.setHeader("Cache-Control", "no-cache");response.setHeader("Expires", "0");// 生成随机4位验证码(字母+数字)String code = RandomStringUtils.randomAlphanumeric(4).toLowerCase();// 加密验证码String encryptedCode = encrypt(code);// 生成图片int width = 100, height = 40;BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);Graphics2D g = image.createGraphics();// 设置背景g.setColor(Color.WHITE);g.fillRect(0, 0, width, height);// 绘制干扰线g.setColor(Color.GRAY);for (int i = 0; i < 10; i++) {int x1 = (int) (Math.random() * width);int y1 = (int) (Math.random() * height);int x2 = (int) (Math.random() * width);int y2 = (int) (Math.random() * height);g.drawLine(x1, y1, x2, y2);}// 绘制验证码g.setColor(Color.BLACK);g.setFont(new Font("Arial", Font.BOLD, 30));g.drawString(code, 15, 30);g.dispose();// 将图片转换为Base64编码java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();ImageIO.write(image, "PNG", baos);byte[] imageBytes = baos.toByteArray();String base64Image = Base64.getEncoder().encodeToString(imageBytes);Map<String, Object> result = new HashMap<>();result.put("image", base64Image);result.put("encryptedCode", encryptedCode);return result;}@PostMapping("/verify")public Map<String, Object> verifyCaptcha(@RequestBody Map<String, String> requestBody) {Map<String, Object> result = new HashMap<>();String inputCode = requestBody.get("code");String encryptedCode = requestBody.get("encryptedCode");try {// 解密验证码String decryptedCode = decrypt(encryptedCode);if (!decryptedCode.equalsIgnoreCase(inputCode)) {result.put("code", 500);result.put("msg", "验证码错误");} else {result.put("code", 200);result.put("msg", "验证码验证通过");}} catch (Exception e) {result.put("code", 500);result.put("msg", "验证码验证出错");}return result;}// 加密方法private String encrypt(String data) throws Exception {SecretKeySpec secretKey = new SecretKeySpec(SECRET_KEY.getBytes(), ALGORITHM);Cipher cipher = Cipher.getInstance(ALGORITHM);cipher.init(Cipher.ENCRYPT_MODE, secretKey);byte[] encryptedBytes = cipher.doFinal(data.getBytes());return Base64.getEncoder().encodeToString(encryptedBytes);}// 解密方法private String decrypt(String encryptedData) throws Exception {SecretKeySpec secretKey = new SecretKeySpec(SECRET_KEY.getBytes(), ALGORITHM);Cipher cipher = Cipher.getInstance(ALGORITHM);cipher.init(Cipher.DECRYPT_MODE, secretKey);byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));return new String(decryptedBytes);}
}

前端

在网页要渲染的样式

 <!-- 添加验证码输入框和显示验证码的图片 --><div class="text"><input v-model="code" class="act" placeholder="验证码" /><img :src="captchaImage" @click="refreshCaptcha" alt="验证码" style="cursor: pointer; vertical-align: middle; margin-left: 10px;"></div>

逻辑处理

//在data里加入
data() {return {captchaImage: '', // 新增:用于存储验证码图片的 Base64 编码encryptedCode: '' // 新增:用于存储加密的验证码}},methods: {// 刷新验证码async refreshCaptcha() {try {const { data } = await request.get(`http://localhost:8088/api/online-travel-sys/v1.0/captcha/get`);this.captchaImage = `data:image/png;base64,${data.image}`;this.encryptedCode = data.encryptedCode;this.code = ''; // 刷新验证码时清空输入框} catch (error) {console.error('获取验证码出错:', error);}},async login() {if (!this.act || !this.pwd) {this.$swal.fire({title: '填写校验',text: '账号或密码不能为空',icon: 'error',showConfirmButton: false,timer: DELAY_TIME,});return;}if (!this.code) {this.$swal.fire({title: '填写校验',text: '验证码不能为空',icon: 'error',showConfirmButton: false,timer: DELAY_TIME,});return;}// 先验证验证码是否正确try {const { data } = await request.post(`http://localhost:8088/api/online-travel-sys/v1.0/captcha/verify`, { code: this.code, encryptedCode: this.encryptedCode });if (data.code !== 200) {this.$swal.fire({title: '验证码错误',text: data.msg,icon: 'error',showConfirmButton: false,timer: DELAY_TIME,});this.refreshCaptcha(); // 刷新验证码return;}} catch (error) {console.error('验证码验证请求错误:', error);this.$message.error('验证码验证出错,请重试!');this.refreshCaptcha(); // 刷新验证码return;}}

完整的前端代码

<template><div class="login-container"><div class="login-panel"><div class="logo"><Logo :bag="colorLogo" sysName="旅友请上车"/></div><div class="text"><input v-model="act" class="act" placeholder="账号" /></div><div class="text"><input v-model="pwd" class="pwd" type="password" placeholder="密码" /></div><!-- 添加验证码输入框和显示验证码的图片 --><div class="text"><input v-model="code" class="act" placeholder="验证码" /><img :src="captchaImage" @click="refreshCaptcha" alt="验证码" style="cursor: pointer; vertical-align: middle; margin-left: 10px;"></div><div><span class="login-btn" @click="login">立即登录</span></div><div class="tip"><p>没有账号?<span class="no-act" @click="toDoRegister">点此注册</span></p></div></div></div>
</template><script>
const ADMIN_ROLE = 1;
const USER_ROLE = 2;
const DELAY_TIME = 1300;
import request from "@/utils/request.js";
import { setToken } from "@/utils/storage.js";
import md5 from 'js-md5';
import Logo from '@/components/Logo.vue';
export default {name: "Login",components: { Logo },data() {return {act: '',pwd: '',code: '', // 新增:用于存储用户输入的验证码colorLogo: 'rgb(38,38,38)',captchaImage: '', // 新增:用于存储验证码图片的 Base64 编码encryptedCode: '' // 新增:用于存储加密的验证码}},created() {// 页面加载时初始化验证码this.refreshCaptcha();},methods: {// 跳转注册页面toDoRegister(){this.$router.push('/register');},// 刷新验证码async refreshCaptcha() {try {const { data } = await request.get(`http://localhost:8088/api/online-travel-sys/v1.0/captcha/get`);this.captchaImage = `data:image/png;base64,${data.image}`;this.encryptedCode = data.encryptedCode;this.code = ''; // 刷新验证码时清空输入框} catch (error) {console.error('获取验证码出错:', error);}},async login() {if (!this.act || !this.pwd) {this.$swal.fire({title: '填写校验',text: '账号或密码不能为空',icon: 'error',showConfirmButton: false,timer: DELAY_TIME,});return;}if (!this.code) {this.$swal.fire({title: '填写校验',text: '验证码不能为空',icon: 'error',showConfirmButton: false,timer: DELAY_TIME,});return;}// 先验证验证码是否正确try {const { data } = await request.post(`http://localhost:8088/api/online-travel-sys/v1.0/captcha/verify`, { code: this.code, encryptedCode: this.encryptedCode });if (data.code !== 200) {this.$swal.fire({title: '验证码错误',text: data.msg,icon: 'error',showConfirmButton: false,timer: DELAY_TIME,});this.refreshCaptcha(); // 刷新验证码return;}} catch (error) {console.error('验证码验证请求错误:', error);this.$message.error('验证码验证出错,请重试!');this.refreshCaptcha(); // 刷新验证码return;}const hashedPwd = md5(md5(this.pwd));const paramDTO = { userAccount: this.act, userPwd: hashedPwd };try {const { data } = await request.post(`user/login`, paramDTO);if (data.code !== 200) {this.$swal.fire({title: '登录失败',text: data.msg,icon: 'error',showConfirmButton: false,timer: DELAY_TIME,});return;}setToken(data.data.token);// 使用Swal通知登录成功,延迟后跳转// this.$swal.fire({//     title: '登录成功',//     text: '即将进入系统...',//     icon: 'success',//     showConfirmButton: false,//     timer: DELAY_TIME,// });// 根据角色延迟跳转setTimeout(() => {const { role } = data.data;this.navigateToRole(role);}, DELAY_TIME);} catch (error) {console.error('登录请求错误:', error);this.$message.error('登录请求出错,请重试!');}},navigateToRole(role) {switch (role) {case ADMIN_ROLE:this.$router.push('/admin');break;case USER_ROLE:this.$router.push('/user');break;default:console.warn('未知的角色类型:', role);break;}},}
};
</script><style lang="scss" scoped>
* {user-select: none;
}
.login-container {width: 100%;min-height: 100vh;background-color: rgb(255, 255, 255);display: flex;/* 启用Flexbox布局 */justify-content: center;/* 水平居中 */align-items: center;/* 垂直居中 */flex-direction: column;/* 如果需要垂直居中,确保子元素也是这样排列 */.login-panel {width: 313px;height: auto;padding: 40px 30px 16px 30px;border-radius: 10px;box-shadow: 0 4px 6px rgba(36, 36, 36, 0.1), 0 1px 3px rgba(40, 40, 40, 0.06);.logo {margin: 10px 0 30px 0;}.act,.pwd {margin: 8px 0;height: 53px;line-height: 53px;width: 100%;padding: 0 8px;box-sizing: border-box;border: 1px solid rgb(232, 230, 230);border-radius: 5px;font-size: 18px;padding: 0 15px;margin-top: 13px;}.act:focus,.pwd:focus {outline: none;border: 1px solid rgb(206, 205, 205);transition: 1.2s;}.role {display: inline-block;color: rgb(30, 102, 147);font-size: 14px;padding-right: 10px;}}.login-btn {display: inline-block;text-align: center;border-radius: 3px;margin-top: 20px;height: 43px;line-height: 43px;width: 100%;background-color: rgb(155, 191, 93);font-size: 14px !important;border: none;color: rgb(250,250,250);padding: 0 !important;cursor: pointer;user-select: none;}.tip {margin: 20px 0;p {padding: 3px 0;margin: 0;font-size: 14px;color: #647897;i{margin-right: 3px;}span {color: #3b3c3e;border-radius: 2px;margin: 0 6px;}.no-act:hover{color: #3e77c2;cursor: pointer;}}}}
</style>

结果展示

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词