前言
為了防止世界被破壞,為了守護(hù)世界的和平。。。說(shuō)錯(cuò)了,重來(lái)~
為了防止驗(yàn)證系統(tǒng)被暴力破解,很多系統(tǒng)都增加了驗(yàn)證碼效驗(yàn),比較常見(jiàn)的就是圖片二維碼,業(yè)內(nèi)比較安全的是短信驗(yàn)證碼,當(dāng)然還有一些拼圖驗(yàn)證碼,加入人工智能的二維碼等等,我們今天的主題就是前后端分離的圖片二維碼登錄方案。
基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶(hù)小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶(hù)、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
- 項(xiàng)目地址:https://github.com/YunaiV/ruoyi-vue-pro
- 視頻教程:https://doc.iocoder.cn/video/
前后端未分離的驗(yàn)證碼登錄方案
傳統(tǒng)的項(xiàng)目大都是基于session交互的,前后端都在一個(gè)項(xiàng)目里面,比如傳統(tǒng)的SSH項(xiàng)目或者一些JSP系統(tǒng),當(dāng)前端頁(yè)面觸發(fā)到獲取驗(yàn)證碼請(qǐng)求,可以將驗(yàn)證碼里面的信息存在上下文中,所以登錄的時(shí)候只需要 用戶(hù)名
、密碼
、驗(yàn)證碼
即可。
驗(yàn)證碼生成流程如下

登錄驗(yàn)證流程如下

可以發(fā)現(xiàn),整個(gè)登錄流程還是依賴(lài)session上下文的,并且由后端調(diào)整頁(yè)面。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶(hù)小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶(hù)、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
前后端分離的驗(yàn)證碼登錄方案
隨著系統(tǒng)和業(yè)務(wù)的不停升級(jí),前后端代碼放在一起的項(xiàng)目越來(lái)越臃腫,已經(jīng)無(wú)法快速迭代和職責(zé)區(qū)分了,于是紛紛投入了前后端分離的懷抱,發(fā)現(xiàn)代碼和職責(zé)分離以后,開(kāi)發(fā)效率越來(lái)越高了,功能迭代還越來(lái)越快,但是以前的驗(yàn)證碼登錄方案就要更改了。
驗(yàn)證碼生成流程如下

對(duì)比原來(lái)的方案,增加了redis中間件,不再是存在session里面了,但是后面怎么區(qū)分這個(gè)驗(yàn)證碼是這個(gè)請(qǐng)求生成的呢?所以我們加入了唯一標(biāo)識(shí)符來(lái)區(qū)分
登錄驗(yàn)證流程如下

可以發(fā)現(xiàn),基于前后端分離的分布式項(xiàng)目登錄方案對(duì)比原來(lái),加了一個(gè)redis中間件和token返回,不再依賴(lài)上下文session,并且頁(yè)面調(diào)整也是由后端換到了前端
動(dòng)手?jǐn)]輪子
基于驗(yàn)證碼的輪子還是挺多的,本文就以Kaptcha
這個(gè)項(xiàng)目為例,通過(guò)springboot項(xiàng)目集成Kaptcha
來(lái)實(shí)現(xiàn)驗(yàn)證碼生成和登錄方案。
Kaptcha介紹
Kaptcha是一個(gè)基于SimpleCaptcha的驗(yàn)證碼開(kāi)源項(xiàng)目
我找的這個(gè)輪子是基于SimpleCaptcha二次封裝的,maven依賴(lài)如下
<dependency>
<groupId>com.github.pengglegroupId>
<artifactId>kaptchaartifactId>
<version>2.3.2version>
dependency>
新建項(xiàng)目并加入依賴(lài)
依賴(lài)主要有 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配置類(lèi)RedisConfig
@Configuration
publicclassRedisConfig{
@Bean
publicRedisTemplateredisTemplate(LettuceConnectionFactoryredisConnectionFactory) {
RedisTemplateredisTemplate=newRedisTemplate();
redisTemplate.setKeySerializer(newStringRedisSerializer());
redisTemplate.setValueSerializer(newGenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(newStringRedisSerializer());
redisTemplate.setHashValueSerializer(newGenericJackson2JsonRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory);
returnredisTemplate;
}
}
驗(yàn)證碼配置類(lèi)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");可以重寫(xiě)實(shí)現(xiàn)類(lèi)
properties.setProperty("kaptcha.noise.impl","com.google.code.kaptcha.impl.NoNoise");
Configconfig=newConfig(properties);
defaultKaptcha.setConfig(config);
returndefaultKaptcha;
}
驗(yàn)證碼控制層CaptchaController
為了方便代碼寫(xiě)一塊了,講究看
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{
//生成文字驗(yàn)證碼
Stringcontent=producer.createText();
//生成圖片驗(yàn)證碼
ByteArrayOutputStreamoutputStream=null;
BufferedImageimage=producer.createImage(content);
outputStream=newByteArrayOutputStream();
ImageIO.write(image,"jpg",outputStream);
//對(duì)字節(jié)數(shù)組Base64編碼
BASE64Encoderencoder=newBASE64Encoder();
Stringstr="data:image/jpeg;base64,";
Stringbase64Img=str+encoder.encode(outputStream.toByteArray()).replace("
","").replace("
","");
CaptchaVOcaptchaVO=captchaService.cacheCaptcha(content);
captchaVO.setBase64Img(base64Img);
returncaptchaVO;
}
}
驗(yàn)證碼返回對(duì)象CaptchaVO
packagecom.lzp.kaptcha.vo;
publicclassCaptchaVO{
/**
*驗(yàn)證碼標(biāo)識(shí)符
*/
privateStringcaptchaKey;
/**
*驗(yàn)證碼過(guò)期時(shí)間
*/
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封裝類(lèi) RedisUtils
網(wǎng)上隨意找的,類(lèi)里面注明來(lái)源,將就用,代碼較多就不貼了,文末有代碼獲取
驗(yàn)證碼方法層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){
//生成一個(gè)隨機(jī)標(biāo)識(shí)符
StringcaptchaKey=UUID.randomUUID().toString();
//緩存驗(yàn)證碼并設(shè)置過(guò)期時(shí)間
redisUtils.set(CAPTCHA_KEY.concat(captchaKey),captcha,timeout);
CaptchaVOcaptchaVO=newCaptchaVO();
captchaVO.setCaptchaKey(captchaKey);
captchaVO.setExpire(timeout);
returncaptchaVO;
}
}
用戶(hù)登錄對(duì)象封裝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
這塊我寫(xiě)邏輯代碼了,相信大家都看的懂
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驗(yàn)證碼已過(guò)期
}
if(!loginDTO.getCaptcha().equals(captch)){
//throw驗(yàn)證碼錯(cuò)誤
}
//查詢(xún)用戶(hù)信息
//判斷用戶(hù)是否存在不存在拋出用戶(hù)名密碼錯(cuò)誤
//判斷密碼是否正確,不正確拋出用戶(hù)名密碼錯(cuò)誤
//構(gòu)造返回到前端的用戶(hù)對(duì)象并封裝信息和生成token
returnnewUserVO();
}
}
驗(yàn)證碼獲取和查看


-
代碼
+關(guān)注
關(guān)注
30文章
4886瀏覽量
70255 -
Redis
+關(guān)注
關(guān)注
0文章
385瀏覽量
11320 -
SpringBoot
+關(guān)注
關(guān)注
0文章
175瀏覽量
318
原文標(biāo)題:SpringBoot 分布式驗(yàn)證碼登錄方案
文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
用基于gin框架的Go語(yǔ)言來(lái)實(shí)現(xiàn)手機(jī)號(hào)發(fā)送短信驗(yàn)證碼登錄

織夢(mèng)dedecms登陸提示“驗(yàn)證碼不正確”的完整解決方案
12306圖片驗(yàn)證碼難倒了誰(shuí)?
無(wú)法驗(yàn)證郵箱,總是提示驗(yàn)證碼錯(cuò)誤,驗(yàn)證碼明明是正確的。
打碼平臺(tái)是如何高效的破解市面上各家驗(yàn)證碼平臺(tái)的各種形式驗(yàn)證碼的?
基于加密短信驗(yàn)證碼的移動(dòng)安全支付解決方案

驗(yàn)證碼能保證真的安全嗎?
以一個(gè)真實(shí)網(wǎng)站的驗(yàn)證碼為例,實(shí)現(xiàn)了基于一下KNN的驗(yàn)證碼識(shí)別
驗(yàn)證碼層出不窮?試試這個(gè)自動(dòng)跳過(guò)驗(yàn)證碼的工具
驗(yàn)證碼太麻煩,自動(dòng)跳過(guò)驗(yàn)證碼神器試一試
爬蟲(chóng)實(shí)現(xiàn)目標(biāo)網(wǎng)站驗(yàn)證碼登陸
帶帶弟弟OCR通用驗(yàn)證碼識(shí)別SDK免費(fèi)開(kāi)源版
驗(yàn)證碼到底在驗(yàn)證啥?聊一聊驗(yàn)證碼是怎么為難我們?nèi)祟?lèi)的

Java 中驗(yàn)證碼的使用

評(píng)論