SpringBoot 分布式验证码登录方案
前言
为了防止验证系统被暴力破解,很多系统都增加了验证码效验,比较常见的就是图片二维码,业内比较安全的是短信验证码,当然还有一些拼图验证码,加入人工智能的二维码等等,我们今天的主题就是前后端分离的图片二维码登录方案。
前后端未分离的验证码登录方案
传统的项目大都是基于session交互的,前后端都在一个项目里面,比如传统的SSH项目或者一些JSP系统,当前端页面触发到获取验证码请求,可以将验证码里面的信息存在上下文中,所以登录的时候只需要 用户名、密码、验证码即可。
验证码生成流程如下
登录验证流程如下
可以发现,整个登录流程还是依赖session上下文的,并且由后端调整页面。
前后端分离的验证码登录方案
随着系统和业务的不停升级,前后端代码放在一起的项目越来越臃肿,已经无法快速迭代和职责区分了,于是纷纷投入了前后端分离的怀抱,发现代码和职责分离以后,开发效率越来越高了,功能迭代还越来越快,但是以前的验证码登录方案就要更改了。
验证码生成流程如下
对比原来的方案,增加了redis中间件,不再是存在session里面了,但是后面怎么区分这个验证码是这个请求生成的呢?所以我们加入了唯一标识符来区分
登录验证流程如下
可以发现,基于前后端分离的分布式项目登录方案对比原来,加了一个redis中间件和token返回,不再依赖上下文session,并且页面调整也是由后端换到了前端
动手撸轮子
基于验证码的轮子还是挺多的,本文就以Kaptcha这个项目为例,通过springboot项目集成Kaptcha来实现验证码生成和登录方案。
Kaptcha介绍
Kaptcha是一个基于SimpleCaptcha的验证码开源项目
我找的这个轮子是基于SimpleCaptcha二次封装的,maven依赖如下
<!--Kaptcha是一个基于SimpleCaptcha的验证码开源项目-->
<dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version>
</dependency>
新建项目并加入依赖
依赖主要有 SpringBoot、Kaptcha、Redis
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.lzp</groupId><artifactId>kaptcha</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.0.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><dependencies><!--Kaptcha是一个基于SimpleCaptcha的验证码开源项目--><dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- redis依赖commons-pool 这个依赖一定要添加 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.3</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
Redis配置类RedisConfig
@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory){RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());redisTemplate.setConnectionFactory(redisConnectionFactory);return redisTemplate;}}
验证码配置类KaptchaConfig
@Configuration
public class KaptchaConfig {@Beanpublic DefaultKaptcha producer(){DefaultKaptcha defaultKaptcha = new DefaultKaptcha();Properties properties = new Properties();properties.setProperty("kaptcha.border", "no");properties.setProperty("kaptcha.border.color", "105,179,90");properties.setProperty("kaptcha.textproducer.font.color", "black");properties.setProperty("kaptcha.image.width", "110");properties.setProperty("kaptcha.image.height", "40");properties.setProperty("kaptcha.textproducer.char.string","23456789abcdefghkmnpqrstuvwxyzABCDEFGHKMNPRSTUVWXYZ");properties.setProperty("kaptcha.textproducer.font.size", "30");properties.setProperty("kaptcha.textproducer.char.space","3");properties.setProperty("kaptcha.session.key", "code");properties.setProperty("kaptcha.textproducer.char.length", "4");properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
// properties.setProperty("kaptcha.obscurificator.impl","com.xxx");可以重写实现类properties.setProperty("kaptcha.noise.impl","com.google.code.kaptcha.impl.NoNoise");Config config = new Config(properties);defaultKaptcha.setConfig(config);return defaultKaptcha;}
验证码控制层CaptchaController
为了方便代码写一块了,讲究看
package com.lzp.kaptcha.controller;import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.lzp.kaptcha.service.CaptchaService;
import com.lzp.kaptcha.vo.CaptchaVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import sun.misc.BASE64Encoder;import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;@RestController
@RequestMapping("/captcha")
public class CaptchaController {@Autowiredprivate DefaultKaptcha producer;@Autowiredprivate CaptchaService captchaService;@ResponseBody@GetMapping("/get")public CaptchaVO getCaptcha() throws IOException {// 生成文字验证码String content = producer.createText();// 生成图片验证码ByteArrayOutputStream outputStream = null;BufferedImage image = producer.createImage(content);outputStream = new ByteArrayOutputStream();ImageIO.write(image, "jpg", outputStream);// 对字节数组Base64编码BASE64Encoder encoder = new BASE64Encoder();String str = "data:image/jpeg;base64,";String base64Img = str + encoder.encode(outputStream.toByteArray()).replace("\n", "").replace("\r", "");CaptchaVO captchaVO =captchaService.cacheCaptcha(content);captchaVO.setBase64Img(base64Img);return captchaVO;}}
验证码返回对象CaptchaVO
package com.lzp.kaptcha.vo;public class CaptchaVO {/*** 验证码标识符*/private String captchaKey;/*** 验证码过期时间*/private Long expire;/*** base64字符串*/private String base64Img;public String getCaptchaKey() {return captchaKey;}public void setCaptchaKey(String captchaKey) {this.captchaKey = captchaKey;}public Long getExpire() {return expire;}public void setExpire(Long expire) {this.expire = expire;}public String getBase64Img() {return base64Img;}public void setBase64Img(String base64Img) {this.base64Img = base64Img;}
}
Redis封装类 RedisUtils
网上随意找的,类里面注明来源,将就用,代码较多就不贴了。
验证码方法层CaptchaService
package com.lzp.kaptcha.service;import com.lzp.kaptcha.utils.RedisUtils;
import com.lzp.kaptcha.vo.CaptchaVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;import java.util.UUID;@Service
public class CaptchaService {@Value("${server.session.timeout:300}")private Long timeout;@Autowiredprivate RedisUtils redisUtils;private final String CAPTCHA_KEY = "captcha:verification:";public CaptchaVO cacheCaptcha(String captcha){//生成一个随机标识符String captchaKey = UUID.randomUUID().toString();//缓存验证码并设置过期时间redisUtils.set(CAPTCHA_KEY.concat(captchaKey),captcha,timeout);CaptchaVO captchaVO = new CaptchaVO();captchaVO.setCaptchaKey(captchaKey);captchaVO.setExpire(timeout);return captchaVO;}}
用户登录对象封装LoginDTO
package com.lzp.kaptcha.dto;public class LoginDTO {private String userName;private String pwd;private String captchaKey;private String captcha;public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}public String getPwd() {return pwd;}public void setPwd(String pwd) {this.pwd = pwd;}public String getCaptchaKey() {return captchaKey;}public void setCaptchaKey(String captchaKey) {this.captchaKey = captchaKey;}public String getCaptcha() {return captcha;}public void setCaptcha(String captcha) {this.captcha = captcha;}
}
登录控制层UserController
这块我写逻辑代码了,相信大家都看的懂
package com.lzp.kaptcha.controller;import com.lzp.kaptcha.dto.LoginDTO;
import com.lzp.kaptcha.utils.RedisUtils;
import com.lzp.kaptcha.vo.UserVO;
import org.springframework.beans.factory.annotation.Autowired;
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;@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate RedisUtils redisUtils;@PostMapping("/login")public UserVO login(@RequestBody LoginDTO loginDTO) {Object captch = redisUtils.get(loginDTO.getCaptchaKey());if(captch == null){// throw 验证码已过期}if(!loginDTO.getCaptcha().equals(captch)){// throw 验证码错误}// 查询用户信息//判断用户是否存在 不存在抛出用户名密码错误//判断密码是否正确,不正确抛出用户名密码错误//构造返回到前端的用户对象并封装信息和生成tokenreturn new UserVO();}
}
验证码获取和查看
相关文章:

SpringBoot 分布式验证码登录方案
前言 为了防止验证系统被暴力破解,很多系统都增加了验证码效验,比较常见的就是图片二维码,业内比较安全的是短信验证码,当然还有一些拼图验证码,加入人工智能的二维码等等,我们今天的主题就是前后端分离的…...

vite.config.js文件配置代理设置VITE_APP_BASE_API
.env.development文件 ENV development # base api VITE_APP_BASE_API /dev-api.env.production文件 ENV production # base api VITE_APP_BASE_API /apidefine: {process.env: {VITE_APP_BASE_API: https://xxx.com}},server: {hmr: true, // vue3 vite配置热更新不用手动…...

优橙内推海南专场——5G网络优化(中高级)工程师
可加入就业QQ群:801549240 联系老师内推简历投递邮箱:hrictyc.com 内推公司1:南京华苏科技有限公司 内推公司2:南京欣网通信股份有限公司 内推公司3:广东华讯工程有限公司 南京华苏科技有限公司 南京华苏科技有…...

5083: 【递推】走方格
题目描述 在平面上有一些二维的点阵。 这些点的编号就像二维数组的编号一样,从上到下依次为第 1 至第 n 行,从左到右依次为第 1 至第 m 列,每一个点可以用行号和列号来表示。 现在有个人站在第 1 行第 1 列,要走到第 n 行第 m …...

多种方式计算当天与另一天的间隔天数 Java实现
这里不会记录纯原生写法,因为现在基本都是被工具类封装好的,所以会记录好用的工具类来简化开发,当然自己可以研究写一个年月日各自做减法的纯原生工具类。 踩坑处(System.currentTimeMillis) 这里指的是使用System.currentTimeMillis()方法。…...

Python基础学习004——for循环与字符串
""" 1.for循环基本语法 2.做指定次数的循环,range()函数 3.continue的使用 4.字符串的定义与使用:转义符,原生字符 5.获取字符串长度,字符串索引的使用 6.切片,翻转字符串 7.字符串的查找find 8.字符串的替换replace 9.字符串的拆分split 10.字符串的链接join &…...

【发展史】鼠标的发展史
最早可以追溯到1952年,皇家加拿大海军将5针保龄球放在能够侦测球面转动的硬件上,这个硬件再将信息转化成光标在屏幕上移动,用作军事计算机输入。这是我们能够追溯到的最早的依靠手部运动进行光标移动的输入设备。但当时这个东西不叫鼠标&…...

ThinkPHP6 多应用模式之验证码模块的配置与验证
Thinphp6 官方的验证码模块的配置是有问题的,或者说需要手工配置。 在配置期间,我尝试了多种(包括按照官方文档、路由等)方法都验证失败。 存在2个问题: 1、多应用模式下,验证码的配置文件依然读取全局的…...

数据结构笔记——树和图(王道408)(持续更新)
文章目录 传送门前言树(重点)树的数据结构定义性质 二叉树的数据结构定义性质储存结构 二叉树算法先中后序遍历层次展开法递归模拟法 层次遍历遍历序列逆向构造二叉树 线索二叉树(难点)定义线索化的本质 二叉树线索化线索二叉树中…...

Redis 主从
目录 编辑一、构建主从架构 1、集群结构 2、准备实例和配置 (1)创建目录 (2)修改原始配置 (3)拷贝配置文件到每个实例目录 (4)修改每个实例的端口,工作目录 &a…...

嵌入式学习笔记(63)位操作实战
(1)给定一个整型数a,设置a的bit3,保证其他位不变。 a | (1<<3) (2)给定一个整形数a,设置a的bit3~bit7,保持其他位不变 a | (0x1f<<3) (3)给定一个整型数a,清除a的bit15,保证其他位不变。 a …...

8位机adc采样正弦波频率
相位/峰峰值高电平? 检 测峰值电压? y 开始计数 检测零电压 y 计数器值16ms/20ms 斩波开x关x延时 tt 频率 1/2t 电路 增减常数 aT...

react中使用监听
在 React 中,您可以使用 addEventListener 函数来监听事件。以下是一个示例: import React, { useRef, useEffect } from react;function App() {const inputRef useRef(null);useEffect(() > {inputRef.current.addEventListener(input, handleInp…...

Java基础总结
0、Java语言 1.java和c 2.编译和解释 3.jre和jdk,jvm 简单来说,编译型语言是指编译器针对特定的操作系统将源代码一次性翻译成可被该平台执行的机器码;解释型语言是指解释器对源程序逐行解释成特定平台的机器码并立即执行。 Java 语言既具…...

基于SSM的OA办公系统
末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:Vue 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目:是 目录…...

【第25例】IPD体系进阶:需求分析团队RAT
目录 简介 RAT CSDN学院相关内容推荐 作者简介 简介 RAT是英文Requirement Analysis Team英文首字母的简称,也即需求分析团队,每个产品线都需要设定对应的一个RAT的组织。 RAT主要负责产品领域内需求的分析活动,是RMT的支撑团队: 这个时候可以将RAT细化为PL-RAT团队,…...

5G与无人驾驶:引领未来交通的新潮流
5G与无人驾驶:引领未来交通的新潮流 随着5G技术的快速发展和普及,无人驾驶技术也日益受到人们的关注。5G技术为无人驾驶提供了更高效、更准确、更及时的通信方式,从而改变了我们对交通出行的认知和使用方式。本文将探讨5G技术在无人驾驶领域的…...

FreeRTOS学习2018.6.27
《FreeRTOS学习》 1.freeRTOS基本功能函数: 定义任务:ATaskFunction(); 创建任务:xTaskCreate(); 改优先级:vTaskPrioritySet(); 系统延时:vTaskDelay(); 精确延时:vTaskDelayUntil(); 空闲任务钩子函数&…...

【异常】理解Java中的异常处理机制
标题:理解Java中的异常处理机制 摘要: 异常处理是Java编程中的重要概念之一,它可以帮助开发者识别和处理程序运行过程中的错误和异常情况。本文将深入探讨Java中的异常处理机制,包括异常的分类、异常处理的语法和最佳实践。通过示…...

很久没写JAVA程序了,原来用GMAIL发送邮件这么简单
写完代码,配置了GMAIL,死活发布出去,碰到了错误535-5.7.8 Username and Password not accepted. 首先先写代码,然后配置GMAIL. 第一写代码: 当你需要在 Spring Boot 中实现邮件通知时,你可以使用 Spring 的 JavaMailSender 来发送电子邮件。首先,确保你的 Spring Boo…...

Spring Security获得认证流程解析(示意图)
建议先看完Spring Security总体架构介绍和Spring Security认证架构介绍,然后从FilterChainProxy的doFilterInternal函数开始,配合文章进行debug以理解Spring Security认证源码的执行流程。 在之前的Spring Security认证架构介绍中,我们已经知…...

scrapy typeerror: attrs() got an unexpected keyword argument ‘eq‘
问题: scrapy 爬虫程序报错 scrapy typeerror: attrs() got an unexpected keyword argument eq原因: Twisted 版本过高 解决方法: # 安装指定版本 pip install --index https://pypi.mirrors.ustc.edu.cn/simple/ Twisted21.7.0# 几个可…...

非侵入式负荷检测与分解:电力数据挖掘新视角
电力数据挖掘 概述案例背景分析目标分析过程数据准备数据探索缺失值处理 属性构造设备数据周波数据模型训练 性能度量推荐阅读 主页传送门:📀 传送 概述 摘要:本案例将根据已收集到的电力数据,深度挖掘各电力设备的电流、电压和功…...

抽丝剥茧,Redis使用事件总线EventBus或AOP优化健康检测
目录 前言 Lettuce 什么是事件总线EventBus? Connected Connection activated Disconnected Connection deactivated Reconnect failed 使用 一种另类方法—AOP 具体实现 前言 在上一篇深入浅出,SpringBoot整合Quartz实现定时任务与Redis健康…...

【Tailwind CSS】当页面内容过少,怎样让footer保持在屏幕底部?
footer通常写版权信息等,显示在页面底部。如果页面内容过少,则footer会出现在屏幕中间位置,很尴尬。在 Tailwind 中,你可以使用flex来实现footer保持在屏幕或页面底部。 代码: <div class"flex flex-col min…...

Docker基础管理
这里写目录标题 Docker基础管理一.Docker 概述1.Docker介绍2.Docker与虚拟机的区别3.容器在内核中支持2种重要技术4.Docker核心概念 二.安装Docker1.安装依赖包2.配置文件及相关 三.Docker操作1.镜像操作2.容器操作 Docker基础管理 一.Docker 概述 1.Docker介绍 Docker是一个…...

基于YOLOv8模型的烟雾目标检测系统(PyTorch+Pyside6+YOLOv8模型)
摘要:基于YOLOv8模型的烟雾目标检测系统可用于日常生活中检测与定位烟雾目标,利用深度学习算法可实现图片、视频、摄像头等方式的目标检测,另外本系统还支持图片、视频等格式的结果可视化与结果导出。本系统采用YOLOv8目标检测算法训练数据集…...

【代码随想录01】数组总结
抄去吧,保存去吧!...

(SpringBoot)第二章:Spring创建和使用
文章目录 一:Sring创建(1)创建一个Maven项目(2)添加Spring框架支持(3)添加启动类二:存储Bean(1)创建Bean(2)将Bean注册到Spring中三:获取并使用Bean(1)创建Spring上下文(2)获取指定Bean(3)使用Bean注意:在Java中对象也叫做Bean,所以后续文章中用Bean代替对…...

力扣刷题 day56:10-26
1.解码异或后的数组 未知 整数数组 arr 由 n 个非负整数组成。 经编码后变为长度为 n - 1 的另一个整数数组 encoded ,其中 encoded[i] arr[i] XOR arr[i 1] 。例如,arr [1,0,2,1] 经编码后得到 encoded [1,2,3] 。 给你编码后的数组 encoded 和原…...