前言
为了防止世界被破坏,为了守护世界的和平。。。说错了,重来~
为了防止验证系统被暴力破解,很多系统都增加了验证码效验,比较常见的就是图片二维码,业内比较安全的是短信验证码,当然还有一些拼图验证码,加入人工智能的二维码等等,我们今天的主题就是前后端分离的图片二维码登录方案。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
- 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
- 视频教程:https://doc.iocoder.cn/video/
前后端未分离的验证码登录方案
传统的项目大都是基于session交互的,前后端都在一个项目里面,比如传统的SSH项目或者一些JSP系统,当前端页面触发到获取验证码请求,可以将验证码里面的信息存在上下文中,所以登录的时候只需要 用户名
、密码
、验证码
即可。
验证码生成流程如下
登录验证流程如下
可以发现,整个登录流程还是依赖session上下文的,并且由后端调整页面。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
前后端分离的验证码登录方案
随着系统和业务的不停升级,前后端代码放在一起的项目越来越臃肿,已经无法快速迭代和职责区分了,于是纷纷投入了前后端分离的怀抱,发现代码和职责分离以后,开发效率越来越高了,功能迭代还越来越快,但是以前的验证码登录方案就要更改了。
验证码生成流程如下
对比原来的方案,增加了redis中间件,不再是存在session里面了,但是后面怎么区分这个验证码是这个请求生成的呢?所以我们加入了唯一标识符来区分
登录验证流程如下
可以发现,基于前后端分离的分布式项目登录方案对比原来,加了一个redis中间件和token返回,不再依赖上下文session,并且页面调整也是由后端换到了前端
动手撸轮子
基于验证码的轮子还是挺多的,本文就以Kaptcha
这个项目为例,通过springboot项目集成Kaptcha
来实现验证码生成和登录方案。
Kaptcha介绍
Kaptcha是一个基于SimpleCaptcha的验证码开源项目
我找的这个轮子是基于SimpleCaptcha二次封装的,maven依赖如下
<dependency>
<groupId>com.github.pengglegroupId>
<artifactId>kaptchaartifactId>
<version>2.3.2version>
dependency>
新建项目并加入依赖
依赖主要有 SpringBoot、Kaptcha、Redis
pom.xml
<projectxmlns="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.0http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.lzpgroupId>
<artifactId>kaptchaartifactId>
<version>1.0-SNAPSHOTversion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.0.RELEASEversion>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>com.github.pengglegroupId>
<artifactId>kaptchaartifactId>
<version>2.3.2version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.3version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
Redis配置类RedisConfig
@Configuration
publicclassRedisConfig{
@Bean
publicRedisTemplateredisTemplate(LettuceConnectionFactoryredisConnectionFactory) {
RedisTemplateredisTemplate=newRedisTemplate();
redisTemplate.setKeySerializer(newStringRedisSerializer());
redisTemplate.setValueSerializer(newGenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(newStringRedisSerializer());
redisTemplate.setHashValueSerializer(newGenericJackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory);
returnredisTemplate;
}
}
验证码配置类KaptchaConfig
@Configuration
publicclassKaptchaConfig{
@Bean
publicDefaultKaptchaproducer(){
DefaultKaptchadefaultKaptcha=newDefaultKaptcha();
Propertiesproperties=newProperties();
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");
Configconfig=newConfig(properties);
defaultKaptcha.setConfig(config);
returndefaultKaptcha;
}
验证码控制层CaptchaController
为了方便代码写一块了,讲究看
packagecom.lzp.kaptcha.controller;
importcom.google.code.kaptcha.impl.DefaultKaptcha;
importcom.lzp.kaptcha.service.CaptchaService;
importcom.lzp.kaptcha.vo.CaptchaVO;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.web.bind.annotation.GetMapping;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.ResponseBody;
importorg.springframework.web.bind.annotation.RestController;
importsun.misc.BASE64Encoder;
importjavax.imageio.ImageIO;
importjava.awt.image.BufferedImage;
importjava.io.ByteArrayOutputStream;
importjava.io.IOException;
@RestController
@RequestMapping("/captcha")
publicclassCaptchaController{
@Autowired
privateDefaultKaptchaproducer;
@Autowired
privateCaptchaServicecaptchaService;
@ResponseBody
@GetMapping("/get")
publicCaptchaVOgetCaptcha()throwsIOException{
//生成文字验证码
Stringcontent=producer.createText();
//生成图片验证码
ByteArrayOutputStreamoutputStream=null;
BufferedImageimage=producer.createImage(content);
outputStream=newByteArrayOutputStream();
ImageIO.write(image,"jpg",outputStream);
//对字节数组Base64编码
BASE64Encoderencoder=newBASE64Encoder();
Stringstr="data:image/jpeg;base64,";
Stringbase64Img=str+encoder.encode(outputStream.toByteArray()).replace("
","").replace("
","");
CaptchaVOcaptchaVO=captchaService.cacheCaptcha(content);
captchaVO.setBase64Img(base64Img);
returncaptchaVO;
}
}
验证码返回对象CaptchaVO
packagecom.lzp.kaptcha.vo;
publicclassCaptchaVO{
/**
*验证码标识符
*/
privateStringcaptchaKey;
/**
*验证码过期时间
*/
privateLongexpire;
/**
*base64字符串
*/
privateStringbase64Img;
publicStringgetCaptchaKey(){
returncaptchaKey;
}
publicvoidsetCaptchaKey(StringcaptchaKey){
this.captchaKey=captchaKey;
}
publicLonggetExpire(){
returnexpire;
}
publicvoidsetExpire(Longexpire){
this.expire=expire;
}
publicStringgetBase64Img(){
returnbase64Img;
}
publicvoidsetBase64Img(Stringbase64Img){
this.base64Img=base64Img;
}
}
Redis封装类 RedisUtils
网上随意找的,类里面注明来源,将就用,代码较多就不贴了,文末有代码获取
验证码方法层CaptchaService
packagecom.lzp.kaptcha.service;
importcom.lzp.kaptcha.utils.RedisUtils;
importcom.lzp.kaptcha.vo.CaptchaVO;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.beans.factory.annotation.Value;
importorg.springframework.stereotype.Service;
importjava.util.UUID;
@Service
publicclassCaptchaService{
@Value("${server.session.timeout:300}")
privateLongtimeout;
@Autowired
privateRedisUtilsredisUtils;
privatefinalStringCAPTCHA_KEY="captcha";
publicCaptchaVOcacheCaptcha(Stringcaptcha){
//生成一个随机标识符
StringcaptchaKey=UUID.randomUUID().toString();
//缓存验证码并设置过期时间
redisUtils.set(CAPTCHA_KEY.concat(captchaKey),captcha,timeout);
CaptchaVOcaptchaVO=newCaptchaVO();
captchaVO.setCaptchaKey(captchaKey);
captchaVO.setExpire(timeout);
returncaptchaVO;
}
}
用户登录对象封装LoginDTO
packagecom.lzp.kaptcha.dto;
publicclassLoginDTO{
privateStringuserName;
privateStringpwd;
privateStringcaptchaKey;
privateStringcaptcha;
publicStringgetUserName(){
returnuserName;
}
publicvoidsetUserName(StringuserName){
this.userName=userName;
}
publicStringgetPwd(){
returnpwd;
}
publicvoidsetPwd(Stringpwd){
this.pwd=pwd;
}
publicStringgetCaptchaKey(){
returncaptchaKey;
}
publicvoidsetCaptchaKey(StringcaptchaKey){
this.captchaKey=captchaKey;
}
publicStringgetCaptcha(){
returncaptcha;
}
publicvoidsetCaptcha(Stringcaptcha){
this.captcha=captcha;
}
}
登录控制层UserController
这块我写逻辑代码了,相信大家都看的懂
packagecom.lzp.kaptcha.controller;
importcom.lzp.kaptcha.dto.LoginDTO;
importcom.lzp.kaptcha.utils.RedisUtils;
importcom.lzp.kaptcha.vo.UserVO;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.web.bind.annotation.PostMapping;
importorg.springframework.web.bind.annotation.RequestBody;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
publicclassUserController{
@Autowired
privateRedisUtilsredisUtils;
@PostMapping("/login")
publicUserVOlogin(@RequestBodyLoginDTOloginDTO){
Objectcaptch=redisUtils.get(loginDTO.getCaptchaKey());
if(captch==null){
//throw验证码已过期
}
if(!loginDTO.getCaptcha().equals(captch)){
//throw验证码错误
}
//查询用户信息
//判断用户是否存在不存在抛出用户名密码错误
//判断密码是否正确,不正确抛出用户名密码错误
//构造返回到前端的用户对象并封装信息和生成token
returnnewUserVO();
}
}
验证码获取和查看
-
代码
+关注
关注
30文章
4741浏览量
68324 -
Redis
+关注
关注
0文章
371浏览量
10843 -
SpringBoot
+关注
关注
0文章
173浏览量
167
原文标题:SpringBoot 分布式验证码登录方案
文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论