当前位置: 首页 > article >正文

在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>

结果展示


http://www.kler.cn/a/546232.html

相关文章:

  • 【linux】文件与目录命令 - ln
  • 渗透测试工具:SQLmap安装教程及使用
  • 【算法专场】哈希表
  • 庞氏骗局(Ponzi Scheme):金融投资与公司经营中的隐形陷阱(中英双语)
  • 在我的世界地下城开发mod的第一天
  • 怎么才能DeepSeek批量写作和内容导出?
  • 傅里叶变换推导
  • 【黑马点评优化】1-使用JWT登录认证+redis实现自动续期
  • Django 进行数据库操作(ORM框架+mysqlclient+mysql)
  • 2.14学习记录
  • 网络安全RSA加密
  • 美团 字节 view
  • 比较34个结构的分类准确率
  • 【Linux】--- 基础开发工具之yum/apt、vim、gcc/g++的使用
  • C++算法竞赛基础语法-9
  • Linux 设备驱动 -- I2C 子系统快速入门
  • 数据可视化+SpringBoot+协同过滤推荐算法的美食点餐管理平台
  • Training for Computer Use
  • 大脑网络与智力:基于图神经网络的静息态fMRI数据分析方法|文献速递-医学影像人工智能进展
  • 计算机视觉-局部特征