国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Java21 + SpringBoot3集成easy-captcha實現(xiàn)驗證碼顯示和登錄校驗

這篇具有很好參考價值的文章主要介紹了Java21 + SpringBoot3集成easy-captcha實現(xiàn)驗證碼顯示和登錄校驗。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

前言

近日心血來潮想做一個開源項目,目標是做一款可以適配多端、功能完備的模板工程,包含后臺管理系統(tǒng)和前臺系統(tǒng),開發(fā)者基于此項目進行裁剪和擴展來完成自己的功能開發(fā)。

本項目為前后端分離開發(fā),后端基于Java21SpringBoot3開發(fā),后端使用Spring Security、JWT、Spring Data JPA等技術棧,前端提供了vue、angularreact、uniapp、微信小程序等多種腳手架工程。

本文主要介紹在SpringBoot3項目中如何集成easy-captcha生成驗證碼,JDK版本是Java21,前端使用Vue3開發(fā)。

項目地址:https://gitee.com/breezefaith/fast-alden

相關技術簡介

easy-captcha

easy-captcha是生成圖形驗證碼的Java類庫,支持gif、中文、算術等類型,可用于Java Web、JavaSE等項目。

參考地址:

  • Github:https://github.com/whvcse/EasyCaptcha

Java21 + SpringBoot3集成easy-captcha實現(xiàn)驗證碼顯示和登錄校驗,開發(fā)日記,spring boot,java,vue.js,驗證碼

實現(xiàn)步驟

引入maven依賴

pom.xml中添加easy-captcha以及相關依賴,并引入Lombok用于簡化代碼。

<dependencies>
  <!-- easy-captcha -->
  <dependency>
    <groupId>com.github.whvcse</groupId>
    <artifactId>easy-captcha</artifactId>
    <version>1.6.2</version>
  </dependency>
  <!--    解決easy-captcha算術驗證碼報錯問題    -->
  <dependency>
    <groupId>org.openjdk.nashorn</groupId>
    <artifactId>nashorn-core</artifactId>
    <version>15.4</version>
  </dependency>
  <!-- Lombok -->
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
    <optional>true</optional>
  </dependency>
</dependencies>

筆者使用的JDK版本是Java21,SpringBoot版本是3.2.0,如果不引入nashorn-core,生成驗證碼時會報錯java.lang.NullPointerException: Cannot invoke "javax.script.ScriptEngine.eval(String)" because "engine" is null。有開發(fā)者反饋使用Java 17時也遇到了同樣的問題,手動引入nashorn-core后即可解決該問題。

詳細堆棧和截圖如下:

java.lang.NullPointerException: Cannot invoke "javax.script.ScriptEngine.eval(String)" because "engine" is null
	at com.wf.captcha.base.ArithmeticCaptchaAbstract.alphas(ArithmeticCaptchaAbstract.java:42) ~[easy-captcha-1.6.2.jar:na]
	at com.wf.captcha.base.Captcha.checkAlpha(Captcha.java:156) ~[easy-captcha-1.6.2.jar:na]
	at com.wf.captcha.base.Captcha.text(Captcha.java:137) ~[easy-captcha-1.6.2.jar:na]
	at com.fast.alden.admin.service.impl.AuthServiceImpl.generateVerifyCode(AuthServiceImpl.java:72) ~[classes/:na]
  ......

Java21 + SpringBoot3集成easy-captcha實現(xiàn)驗證碼顯示和登錄校驗,開發(fā)日記,spring boot,java,vue.js,驗證碼

定義實體類

為了方便后端校驗,獲取驗證碼的請求除了要返回驗證碼圖片本身,還要返回一個驗證碼的唯一標識,所以筆者定義了一個實體類VerifyCodeEntity。

/**
 * 驗證碼實體
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class VerifyCodeEntity implements Serializable {
    /**
     * 驗證碼Key
     */
    private String key;

    /**
     * 驗證碼圖片,base64壓縮后的字符串
     */
    private String image;

    /**
     * 驗證碼文本值
     */
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private String text;
}

使用@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)注解可以使text屬性不會被序列化后返回給前端。

為實現(xiàn)登錄功能,還要定義一個登錄參數類LoginParam。

@Data
public class LoginParam {
    /**
     * 用戶名
     */
    private String username;
    /**
     * 密碼
     */
    private String password;
    /**
     * 驗證碼Key
     */
    private String verifyCodeKey;
    /**
     * 驗證碼
     */
    private String verifyCode;
}

定義登錄服務類

在登錄服務類中,我們需要定義以下方法:

  1. 生成驗證碼

    在該方法中使用easy-captcha生成一個驗證碼,生成的驗證碼除了要返回給前端,還需要在后端進行緩存,這樣才能實現(xiàn)前后端的驗證碼校驗。本文中給出了兩種緩存驗證碼的方式,一種是基于RedisTemplate緩存至Redis,一種是緩存至Session,讀者可根據需要選擇性使用,推薦使用**Redis**。在本文附錄中給出了緩存至Session的實現(xiàn)方式。

  2. 登錄

    在登錄方法中首先校驗驗證碼是否正確,然后再校驗用戶名和密碼是否正確,校驗通過后生成Token返回給前端。本文中該方法僅給出驗證碼校驗相關的邏輯,其他邏輯請自行實現(xiàn)。

@Service
public class AuthService {
    private final RedisTemplate<String, Object> redisTemplate;

    public AuthService(
        RedisTemplate<String, Object> redisTemplate
    ) {
        this.redisTemplate = redisTemplate;
    }

    public VerifyCodeEntity generateVerifyCode() throws IOException {
        // 創(chuàng)建驗證碼對象
        Captcha captcha = new ArithmeticCaptcha();

        // 生成驗證碼編號
        String verifyCodeKey = UUID.randomUUID().toString();
        String verifyCode = captcha.text();

        // 獲取驗證碼圖片,構造響應結果
        VerifyCodeEntity verifyCodeEntity = new VerifyCodeEntity(verifyCodeKey, captcha.toBase64(), verifyCode);

        // 存入Redis,設置120s過期
        redisTemplate.opsForValue().set(verifyCodeKey, verifyCode, 120, TimeUnit.SECONDS);

        return verifyCodeEntity;
    }

    public String login(LoginParam param) {
        // 校驗驗證碼
        // 獲取用戶輸入的驗證碼
        String actual = param.getVerifyCode();
        // 判斷驗證碼是否過期
        if (redisTemplate.getExpire(param.getVerifyCodeKey(), TimeUnit.SECONDS) < 0) {
            throw new RuntimeException("驗證碼過期");
        }
        // 從redis讀取驗證碼并刪除緩存
        String expect = (String) redisTemplate.opsForValue().get(param.getVerifyCodeKey());
        redisTemplate.delete(param.getVerifyCodeKey());

        // 比較用戶輸入的驗證碼和緩存中的驗證碼是否一致,不一致則拋錯
        if (!StringUtils.hasText(expect) || !StringUtils.hasText(actual) || !actual.equalsIgnoreCase(expect)) {
            throw new RuntimeException("驗證碼錯誤");
        }

        // 校驗用戶名和密碼,校驗成功后生成token返回給前端,具體邏輯省略
        String token = "";

        return token;
    }
}

定義登錄控制器

/**
 * 登錄控制器
 */
@RestController("/auth")
public class AuthController {
    private final AuthService authService;

    public AuthController(AuthService authService) {
        this.authService = authService;
    }

    /**
     * 獲取驗證碼
     */
    @GetMapping("/verify-code")
    public VerifyCodeEntity generateVerifyCode() throws IOException {
        return authService.generateVerifyCode();
    }

    /**
     * 登錄
     */
    @PostMapping("/login")
    public String login(@RequestBody @Validated LoginParam param) {
        return authService.login(param);
    }
}

前端登錄頁面實現(xiàn)

此前端頁面基于Vue3的組合式API和Element Plus開發(fā),使用Axios向后端發(fā)送請求,因代碼較長,將其放在附錄中,請移步至附錄查看。

測試和驗證

Java21 + SpringBoot3集成easy-captcha實現(xiàn)驗證碼顯示和登錄校驗,開發(fā)日記,spring boot,java,vue.js,驗證碼

總結

本文介紹了如何基于Java21SpringBoot3集成easy-captcha實現(xiàn)驗證碼顯示和登錄校驗,給出了詳細的實現(xiàn)代碼,如有錯誤,還望批評指正。

在后續(xù)實踐中我也是及時更新自己的學習心得和經驗總結,希望與諸位看官一起進步。

附錄

使用Session緩存驗證碼

使用Session緩存驗證碼時還需要借助ScheduledExecutorService、Timer、Quartz等實現(xiàn)一個延遲任務,用于從Session中刪除超時的驗證碼。

@Service
public class AuthService {
    private final ScheduledExecutorService scheduledExecutorService;

    public AuthService(
        ScheduledExecutorService scheduledExecutorService
    ) {
        this.scheduledExecutorService = scheduledExecutorService;
    }

    public VerifyCodeEntity generateVerifyCode() throws IOException {
        // 創(chuàng)建驗證碼對象
        Captcha captcha = new ArithmeticCaptcha();

        // 生成驗證碼編號
        String verifyCodeKey = UUID.randomUUID().toString();
        String verifyCode = captcha.text();

        // 獲取驗證碼圖片,構造響應結果
        VerifyCodeEntity verifyCodeEntity = new VerifyCodeEntity(verifyCodeKey, captcha.toBase64(), verifyCode);

        // 存入session,設置120s過期
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpSession session = attributes.getRequest().getSession();
        session.setAttribute(verifyCodeKey, verifyCode);
        // 超時后刪除驗證碼緩存
        // 以下是使用ScheduledExecutorService實現(xiàn)
        scheduledExecutorService.schedule(() -> {
            session.removeAttribute(verifyCode);
        }, 120, TimeUnit.SECONDS);
        // // 以下是使用Timer實現(xiàn)超時后刪除驗證碼
        // Timer timer = new Timer();
        // timer.schedule(new TimerTask() {
        //     @Override
        //     public void run() {
        //         session.removeAttribute(verifyCode);
        //     }
        // }, 120 * 1000L);

        return verifyCodeEntity;
    }

    public String login(LoginParam param) {
        // 校驗驗證碼
        // 獲取用戶輸入的驗證碼
        String actual = param.getVerifyCode();

        // 從Session讀取驗證碼并刪除緩存
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpSession session = attributes.getRequest().getSession();
        String expect = (String) session.getAttribute(param.getVerifyCodeKey());
        session.removeAttribute(param.getVerifyCodeKey());

        // 比較用戶輸入的驗證碼和緩存中的驗證碼是否一致,不一致則拋錯
        if (!StringUtils.hasText(expect) || !StringUtils.hasText(actual) || !actual.equalsIgnoreCase(expect)) {
            throw new RuntimeException("驗證碼錯誤");
        }

        // 校驗用戶名和密碼,校驗成功后生成token返回給前端,具體邏輯省略
        String token = "";

        return token;
    }
}

以上代碼中使用ScheduledExecutorService設置了一個延遲任務,120s后從Session中刪除驗證碼,還需要聲明一個ScheduledExecutorService的Bean。文章來源地址http://www.zghlxwxcb.cn/news/detail-817352.html

/**
 * 線程池配置
 */
@Configuration
public class ThreadPoolConfig {
    /**
     * 核心線程池大小
     */
    private final int corePoolSize = 50;

    @Bean
    public ScheduledExecutorService scheduledExecutorService() {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
}

前端登錄頁面實現(xiàn)代碼

<script setup>
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage, ElForm, ElFormItem, ElInput, ElButton, ElCheckbox } from 'element-plus';
import { CircleCheck, Lock, User, Search, Refresh, Plus, Edit, Delete, View, Upload, Download, Share, Close } from "@element-plus/icons-vue";
import axios, { AxiosError } from 'axios';
import bg from "@/assets/login/bg.png";

const router = useRouter();

const entity = ref({});
const rememberMe = ref(true);
const REMEMBER_ME_KEY = "remember_me";
const formRef = ref();
const loading = ref(false);
const verifyCodeUrl = ref("");

const rules = reactive({
    username: [
        {
            required: true,
            message: '請輸入用戶名',
            trigger: 'blur'
        }
    ],
    password: [
        {
            validator: (rule, value, callback) => {
                if (!value) {
                    callback(new Error("請輸入密碼"));
                } else {
                    callback();
                }
            },
            trigger: "blur"
        }
    ],
    verifyCode: [
        {
            required: true,
            message: '請輸入驗證碼',
            trigger: 'blur'
        },
    ],
});

// 點擊登錄按鈕
const login = async () => {
    const formEl = formRef.value;
    loading.value = true;
    if (!formEl) {
        loading.value = false;
        return;
    }
    await formEl.validate(async (valid, fields) => {
        if (valid) {
            try {
                const res = await login$(entity.value);
                // 從響應中獲取token
                const token = res.data.data;
                if (token) {
                    // 將token存入Pinia,authStore請自行定義
                    // authStore.authenticate({ token });

                    // warning: 此方式直接將用戶名密碼明文存入localStorage,并不安全
                    // todo:尋找更合理方式實現(xiàn)“記住我”
                    if (rememberMe.value) {
                        localStorage.setItem(REMEMBER_ME_KEY, JSON.stringify({
                            username: entity.value.username,
                            password: entity.value.password,
                        }));
                    } else {
                        localStorage.removeItem(REMEMBER_ME_KEY);
                    }

                    ElMessage({ message: "登錄成功", type: "success" });
                    router.push("/");
                }else{
                    ElMessage({ message: "登錄失敗", type: "error" });
                }
            } catch (err) {
                if (err instanceof AxiosError) {
                    const msg = err.response?.data?.message || err.message;
                    ElMessage({ message: msg, type: "error" });
                }
                updateVerifyCode();
                throw err;
            } finally {
                loading.value = false;
            }
        } else {
            loading.value = false;
            return fields;
        }
    });
};

// 獲取驗證碼請求
const getVerifyCode$ = async () => {
    return axios.get(`/api/v1.0/admin/auth/verify-code?timestamp=${new Date().getTime()}`, false);
}

// 登錄請求
const login$ = async (param) => {
    return axios.post(`/api/v1.0/admin/auth/login`, {
        ...param,
    });
}

// 更新驗證碼圖片
const updateVerifyCode = async () => {
    const res = await getVerifyCode$();
    verifyCodeUrl.value = `${res.data.data?.image}`;
    entity.value.verifyCodeKey = res.data.data?.key;
}

/** 使用公共函數,避免`removeEventListener`失效 */
function onkeypress({ code }) {
    if (code === "Enter" || code === "NumpadEnter") {
        login();
    }
}

// 頁面加載時讀取localStorage,如果有記住的用戶名密碼則加載至界面
const load = async () => {
    const tmp = localStorage.getItem(REMEMBER_ME_KEY);
    if (tmp) {
        const e = JSON.parse(tmp);
        entity.value.username = e.username;
        entity.value.password = e.password;
    }
}

onMounted(async () => {
    window.document.addEventListener("keypress", onkeypress);

    updateVerifyCode();

    load();
});

onBeforeUnmount(() => {
    window.document.removeEventListener("keypress", onkeypress);
});

</script>

<template>
    <img class="login-bg" :src="bg" />
    <div class="login-container">
        <div class="login-box">
            <ElForm class="login-form" ref="formRef" :model="entity" :rules="rules" size="large">
                <h3 class="title">后臺管理系統(tǒng)</h3>
                <ElFormItem prop="username">
                    <ElInput clearable v-model="entity.username" placeholder="用戶名/手機號/郵箱" :prefix-icon="User" />
                </ElFormItem>

                <ElFormItem prop="password">
                    <ElInput clearable show-password v-model="entity.password" placeholder="密碼" :prefix-icon="Lock" />
                </ElFormItem>

                <ElFormItem class="verify-code-row" prop="verifyCode">
                    <ElInput clearable v-model="entity.verifyCode" placeholder="驗證碼" :prefix-icon="CircleCheck">
                        <template #append>
                            <img :src="verifyCodeUrl" class="verify-code" @click="updateVerifyCode()" />
                        </template>
                    </ElInput>
                </ElFormItem>

                <ElFormItem>
                    <ElCheckbox v-model="rememberMe" label="記住我"></ElCheckbox>
                </ElFormItem>

                <ElFormItem>
                    <ElButton class="w-full" style="width: 100%" size="default" type="primary" :loading="loading" @click="login()">
                        登錄
                    </ElButton>
                </ElFormItem>
            </ElForm>
        </div>
    </div>
</template>

<style lang="scss">
.login-bg {
    position: fixed;
    height: 100%;
    left: 0;
    bottom: 0;
    z-index: -1;
}

.login-container {
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    position: absolute;
    display: flex;
    justify-items: center;
    justify-content: center;

    .login-box {
        display: flex;
        align-items: center;
        text-align: center;

        .login-form {
            width: 360px;

            .verify-code-row {
                .el-input-group__append {
                    padding: 0;
                }

                .verify-code {
                    height: 40px;
                }
            }
        }
    }
}
</style>

到了這里,關于Java21 + SpringBoot3集成easy-captcha實現(xiàn)驗證碼顯示和登錄校驗的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!

本文來自互聯(lián)網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • JAVA新實戰(zhàn)1:使用vscode+gradle+openJDK21搭建java springboot3項目開發(fā)環(huán)境

    JAVA新實戰(zhàn)1:使用vscode+gradle+openJDK21搭建java springboot3項目開發(fā)環(huán)境

    ? ? ? ? 作為一個干了多年的全棧技術工程師,厭倦了使用盜版IDE,近些年開發(fā)Java一直使用IntelliJ IDEA進行Springboot后端項目開發(fā),對于IntelliJ IDEA 授權問題,一直花錢買學生類的授權,但經常被屏蔽,無法使用,又不舍得花大錢買企業(yè)版,索性不再使用了。決定改用 VsCode+Gr

    2024年02月03日
    瀏覽(22)
  • Java21 + SpringBoot3使用Spring Security時如何在子線程中獲取到認證信息

    Java21 + SpringBoot3使用Spring Security時如何在子線程中獲取到認證信息

    目錄 前言 原因分析 解決方案 方案1:手動設置線程中的認證信息 方案2:使用 DelegatingSecurityContextRunnable 創(chuàng)建線程 方案3:修改 Spring Security 安全策略 通過設置JVM參數修改安全策略 通過 SecurityContextHolder 修改安全策略 總結 近日心血來潮想做一個開源項目,目標是做一款可以適

    2024年02月19日
    瀏覽(21)
  • SpringBoot3集成Zookeeper

    SpringBoot3集成Zookeeper

    標簽:Zookeeper3.8 ,Curator5.5; ZooKeeper是一個集中的服務,用于維護配置信息、命名、提供分布式同步、提供組服務。分布式應用程序以某種形式使用所有這些類型的服務。 1、修改配置文件 2、服務啟動 3、客戶端測幾個增刪查的命令 Curator是一組Java庫,它讓ZooKeeper的使用變得

    2024年01月23日
    瀏覽(18)
  • SpringBoot3集成Kafka

    SpringBoot3集成Kafka

    目錄 一、簡介 二、環(huán)境搭建 1、Kafka部署 2、Kafka測試 3、可視化工具 三、工程搭建 1、工程結構 2、依賴管理 3、配置文件 四、基礎用法 1、消息生產 2、消息消費 五、參考源碼 標簽:Kafka3.Kafka-eagle3; Kafka是一個開源的分布式事件流平臺,常被用于高性能數據管道、流分析、

    2024年02月12日
    瀏覽(16)
  • SpringBoot3集成RocketMq

    SpringBoot3集成RocketMq

    標簽:RocketMq5.Dashboard; RocketMQ因其架構簡單、業(yè)務功能豐富、具備極強可擴展性等特點被廣泛應用,比如金融業(yè)務、互聯(lián)網、大數據、物聯(lián)網等領域的業(yè)務場景; 在 rocketmq-starter 組件中,實際上依賴的是 rocketmq-client 組件的 5.0 版本,由于兩個新版框架間的兼容問題,需要添

    2024年02月12日
    瀏覽(25)
  • SpringBoot3集成PostgreSQL

    SpringBoot3集成PostgreSQL

    標簽:PostgreSQL.Druid.Mybatis.Plus; PostgreSQL是一個功能強大的開源數據庫系統(tǒng),具有可靠性、穩(wěn)定性、數據一致性等特點,且可以運行在所有主流操作系統(tǒng)上,包括Linux、Unix、Windows等。 通過官方文檔可以找到大量描述如何安裝和使用PostgreSQL的信息。 環(huán)境搭建,基于 Centos7 部署

    2024年03月24日
    瀏覽(22)
  • SpringBoot3集成ElasticSearch

    SpringBoot3集成ElasticSearch

    目錄 一、簡介 二、環(huán)境搭建 1、下載安裝包 2、服務啟動 三、工程搭建 1、工程結構 2、依賴管理 3、配置文件 四、基礎用法 1、實體類 2、初始化索引 3、倉儲接口 4、查詢語法 五、參考源碼 標簽:ElasticSearch8.Kibana8; Elasticsearch是一個分布式、RESTful風格的搜索和數據分析引擎

    2024年02月12日
    瀏覽(19)
  • SpringBoot3集成Quartz

    SpringBoot3集成Quartz

    目錄 一、簡介 二、工程搭建 1、工程結構 2、依賴管理 3、數據庫 4、配置文件 三、Quartz用法 1、初始化加載 2、新增任務 3、更新任務 4、暫停任務 5、恢復任務 6、執(zhí)行一次 7、刪除任務 8、任務執(zhí)行 四、參考源碼 標簽:Quartz.Job.Scheduler; Quartz由Java編寫的功能豐富的開源作業(yè)

    2024年02月13日
    瀏覽(16)
  • SpringBoot3集成Redis

    SpringBoot3集成Redis

    目錄 一、簡介 二、工程搭建 1、工程結構 2、依賴管理 3、Redis配置 三、Redis用法 1、環(huán)境搭建 2、數據類型 3、加鎖機制 四、Mybatis緩存 1、基礎配置 2、自定義實現(xiàn) 五、參考源碼 標簽:Redis.Mybatis.Lock; 緩存在項目開發(fā)中,基本上是必選組件之一,Redis作為一個 key-value 存儲系

    2024年02月13日
    瀏覽(23)
  • SpringBoot集成Easy-Es

    SpringBoot集成Easy-Es

    Easy-Es(簡稱EE)是一款基于ElasticSearch(簡稱Es)官方提供的RestHighLevelClient打造的ORM開發(fā)框架,在 RestHighLevelClient 的基礎上,只做增強不做改變,為簡化開發(fā)、提高效率而生 1、添加依賴 2、配置信息 3、啟動類中添加 @EsMapperScan 注解,掃描 Mapper 文件夾 4、實體類和mapper 5、測試 h

    2024年02月02日
    瀏覽(25)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包