vue + SpringBoot + Kaptcha + redis 配置登录验证码


前言

在写登录页面时,看很多的项目都会在登录中加入验证码这一功能,那就让我们来看看如何实现


一、导入jar包

<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

 <!-- 验证码 -->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>

二、安装redis

菜鸟教程 - redis安装
我是直接安装的windows的,安装完成之后,把它启动了

三、编写验证码配置

下面这一段全部复制了粘贴进代码就行了

package org.xjj.xjj.config.kaptcha;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Properties;

@Configuration
public class KaptchaConfig {
    @Bean
    public DefaultKaptcha getDefaultKaptcha() {
        DefaultKaptcha dk = new DefaultKaptcha();
        Properties properties = new Properties();
        // 图片边框
        properties.setProperty("kaptcha.border", "yes");
        // 边框颜色
        properties.setProperty("kaptcha.border.color", "105,179,90");
        // 字体颜色
        properties.setProperty("kaptcha.textproducer.font.color", "red");
        // 图片宽
        properties.setProperty("kaptcha.image.width", "110");
        // 图片高
        properties.setProperty("kaptcha.image.height", "40");
        // 字体大小
        properties.setProperty("kaptcha.textproducer.font.size", "30");
        // session key
        properties.setProperty("kaptcha.session.key", "code");
        // 验证码长度
        properties.setProperty("kaptcha.textproducer.char.length", "4");
        // 字体
        properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
        Config config = new Config(properties);
        dk.setConfig(config);

        return dk;
    }
}

四、编写redis的工具类

package org.xjj.xjj.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class RedisUtils {
    @Autowired
    public RedisTemplate redisTemplate;

    public <T> void set(String key, T value){
        redisTemplate.opsForValue().set(key, value);
    }

    public <T> void set(String key, T value, int timeOut, TimeUnit timeUnit){
        redisTemplate.opsForValue().set(key, value, timeOut, timeUnit);
    }

    public <T> T get(String key){
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    public boolean del(String key){
        return redisTemplate.delete(key);
    }
}

五、编写UUID的工具类

package org.xjj.xjj.utils;

import java.util.UUID;

public class IdUtils {
    /**
     * 获取随机UUID
     * @return
     */
    public static String randomUUID(){
        return UUID.randomUUID().toString();
    }

    /**
     * 获取去掉 "-" 的UUID
     * @return
     */
    public static String simpleUUID(){
        return UUID.randomUUID().toString().replace("-","");
    }
}

六、编写验证码的常量类

package org.xjj.xjj.constant;

public class CaptchaConstants {
    /**
     * 验证码失效时间(分)
     */
    public static final int CAPTCHA_EXPIRATION = 2;

    /**
     * 验证码key前缀
     */
    public static final String CAPTCHA_KEY_PREFIX = "captcha_codes:";
}

七、编写Base64Utils类

package org.xjj.xjj.utils;

public final class Base64Utils {

    static private final int     BASELENGTH           = 128;
    static private final int     LOOKUPLENGTH         = 64;
    static private final int     TWENTYFOURBITGROUP   = 24;
    static private final int     EIGHTBIT             = 8;
    static private final int     SIXTEENBIT           = 16;
    static private final int     FOURBYTE             = 4;
    static private final int     SIGN                 = -128;
    static private final char    PAD                  = '=';
    static final private byte[]  base64Alphabet       = new byte[BASELENGTH];
    static final private char[]  lookUpBase64Alphabet = new char[LOOKUPLENGTH];

    static
    {
        for (int i = 0; i < BASELENGTH; ++i)
        {
            base64Alphabet[i] = -1;
        }
        for (int i = 'Z'; i >= 'A'; i--)
        {
            base64Alphabet[i] = (byte) (i - 'A');
        }
        for (int i = 'z'; i >= 'a'; i--)
        {
            base64Alphabet[i] = (byte) (i - 'a' + 26);
        }

        for (int i = '9'; i >= '0'; i--)
        {
            base64Alphabet[i] = (byte) (i - '0' + 52);
        }

        base64Alphabet['+'] = 62;
        base64Alphabet['/'] = 63;

        for (int i = 0; i <= 25; i++)
        {
            lookUpBase64Alphabet[i] = (char) ('A' + i);
        }

        for (int i = 26, j = 0; i <= 51; i++, j++)
        {
            lookUpBase64Alphabet[i] = (char) ('a' + j);
        }

        for (int i = 52, j = 0; i <= 61; i++, j++)
        {
            lookUpBase64Alphabet[i] = (char) ('0' + j);
        }
        lookUpBase64Alphabet[62] = (char) '+';
        lookUpBase64Alphabet[63] = (char) '/';
    }

    public static String encode(byte[] binaryData)
    {
        if (binaryData == null)
        {
            return null;
        }

        int lengthDataBits = binaryData.length * EIGHTBIT;
        if (lengthDataBits == 0)
        {
            return "";
        }

        int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
        int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
        int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets;
        char encodedData[] = null;

        encodedData = new char[numberQuartet * 4];

        byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;

        int encodedIndex = 0;
        int dataIndex = 0;

        for (int i = 0; i < numberTriplets; i++)
        {
            b1 = binaryData[dataIndex++];
            b2 = binaryData[dataIndex++];
            b3 = binaryData[dataIndex++];

            l = (byte) (b2 & 0x0f);
            k = (byte) (b1 & 0x03);

            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
            byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);

            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f];
        }

        // form integral number of 6-bit groups
        if (fewerThan24bits == EIGHTBIT)
        {
            b1 = binaryData[dataIndex];
            k = (byte) (b1 & 0x03);
            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4];
            encodedData[encodedIndex++] = PAD;
            encodedData[encodedIndex++] = PAD;
        }
        else if (fewerThan24bits == SIXTEENBIT)
        {
            b1 = binaryData[dataIndex];
            b2 = binaryData[dataIndex + 1];
            l = (byte) (b2 & 0x0f);
            k = (byte) (b1 & 0x03);

            byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
            byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);

            encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
            encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2];
            encodedData[encodedIndex++] = PAD;
        }
        return new String(encodedData);
    }
}

八、编写获取验证码控制器

package org.xjj.xjj.controller;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.xjj.xjj.constant.CaptchaConstants;
import org.xjj.xjj.utils.IdUtils;
import org.xjj.xjj.utils.RedisUtils;
import org.xjj.xjj.entity.base.AjaxResult;
import org.xjj.xjj.utils.Base64Utils;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.concurrent.TimeUnit;


@RestController
public class KaptchaController {

    @Autowired
    private DefaultKaptcha defaultKaptcha;

    @Autowired
    private RedisUtils redisCache;

    /**
     * 获取验证码图片
     * 获取UUID标识符
     * @return
     */
    @GetMapping("/captchaImage")
    public AjaxResult captchaImage() {
        AjaxResult ajax = AjaxResult.success();
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        String createText = "";

        try {
            createText = defaultKaptcha.createText();
            BufferedImage bi = defaultKaptcha.createImage(createText);
            ImageIO.write(bi, "jpg", out);
        } catch (Exception e) {
            return AjaxResult.error(e.getMessage());
        }

        byte[] captcha = out.toByteArray();

        String uuid = IdUtils.simpleUUID();
        String captchaKey = CaptchaConstants.CAPTCHA_KEY_PREFIX + uuid;
        redisCache.set(captchaKey, createText, CaptchaConstants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);

        ajax.put("uuid", uuid);
        ajax.put("img", Base64Utils.encode(captcha));

        return ajax;
    }
}

九、vue前端获取验证码

<template>
    <div class="container">
        <div id="login-div" v-loading="loading">
            <p id="login-title">{{ loginTitle }}</p>
            <el-form
                :model="loginForm"
                :rules="loginRules"
                id="login-form"
                ref="ruleForm"
            >
                <el-form-item prop="username">
                    <el-input
                        v-model="loginForm.username"
                        placeholder="用户名"
                        @keyup.enter.native="submitForm('ruleForm')"
                    ></el-input>
                </el-form-item>
                <el-form-item prop="password">
                    <el-input
                        v-model="loginForm.password"
                        show-password
                        placeholder="密码"
                        @keyup.enter.native="submitForm('ruleForm')"
                    ></el-input>
                </el-form-item>
                <el-form-item prop="code">
                    <el-row :gutter="10">
                        <el-col :span="12">
                            <el-input
                                v-model="loginForm.code"
                                placeholder="验证码"
                                @keyup.enter.native="submitForm('ruleForm')"
                            ></el-input>
                        </el-col>
                        <el-col :span="12" style="height: 40px;">
                            <el-image
                                :src="codeUrl"
                                @click="getCode()"
                                style="width: 100%; height: 40px; cursor: pointer;"
                            ></el-image>
                        </el-col>
                    </el-row>
                </el-form-item>
                <el-form-item style="margin-bottom: 10px">
                    <el-checkbox-group v-model="loginForm.rememberMe">
                        <el-checkbox
                            label="记住我"
                            name="rememberMe"
                        ></el-checkbox>
                    </el-checkbox-group>
                </el-form-item>
                <el-form-item>
                    <el-button
                        id="login-submit-button"
                        type="primary"
                        @click="submitForm('ruleForm')"
                        >登录</el-button
                    >
                </el-form-item>
            </el-form>
        </div>
    </div>
</template>

<script>
import { getCodeImgAPI } from "@/api/login.js";
import Cookies from "js-cookie";
import { saveLoginData, removeLoginData } from "@/utils/loginUtils.js";
import { encrypt, decrypt } from "@/utils/passwordUtils.js";

export default {
    data() {
        return {
            loginTitle: "闲简居",
            loginForm: {
                username: "",
                password: "",
                rememberMe: false,
                code: "",
                uuid: ""
            },
            loginRules: {
                username: [
                    { required: true, message: "请输入用户名", trigger: "blur" }
                ],
                password: [
                    { required: true, message: "请输入密码", trigger: "blur" }
                ],
                code: [
                    { required: true, message: "请输入验证码", trigger: "blur" }
                ]
            },
            codeUrl: "",
            loading: false
        };
    },
    methods: {
        submitForm(formName) {
            this.$refs[formName].validate(valid => {
                if (valid) {
                    this.loading = true;

                    if (this.loginForm.rememberMe) {
                        let username = this.loginForm.username;
                        let password = encrypt(this.loginForm.password);
                        let rememberMe = this.loginForm.rememberMe;

                        saveLoginData(username, password, rememberMe);
                    } else {
                        removeLoginData();
                    }

                    this.$store.dispatch("user/login", this.loginForm)
                        .then(() => {
                            this.$router.push("/");
                        })
                        .catch(() => {
                            this.loading = false;
                            this.getCode();
                        });
                }
            });
        },
        getCode() {
            getCodeImgAPI().then(res => {
                this.codeUrl = "data:image/gif;base64," + res.data.img;
                this.loginForm.uuid = res.data.uuid;
            });
        },
        getCookie(){
            const username = Cookies.get("username");
            const password = Cookies.get("password");
            const rememberMe = Cookies.get('rememberMe');

            this.loginForm = {
                username: username === undefined ? this.loginForm.username : username,
                password: password === undefined ? this.loginForm.password : decrypt(password),
                rememberMe: rememberMe === undefined ? this.loginForm.rememberMe : Boolean(rememberMe)
            };
        }
    },
    created() {
        this.getCode();
        this.getCookie();
    }
};
</script>

<style scoped>
#login-div {
    width: 300px;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    padding: 20px;
    background-color: #ffffff;
    border-radius: 10px;
}

#login-title {
    font-size: 25px;
    font-weight: bold;
    text-align: center;
    height: 50px;
    line-height: 50px;
    color: #666666;
}

#login-submit-button {
    width: 100%;
}
</style>

十、结果图

在这里插入图片描述


总结

Base64Utils类是从若依项目拷贝过来的,这个类我也不知道干什么,需要大家伙去了解,其它的配置类就很容易理解了。

大家看到我的配置中并没有设置redis的配置,那是因为SpringBoot已经默认配置好了。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值