1.背景
注冊
-登錄
-修改密碼
一般需要發(fā)送驗(yàn)證碼,但是容易被攻擊惡意調(diào)用。
1.1 什么是短信-郵箱轟炸機(jī)
手機(jī)短信轟炸機(jī)是批量、循環(huán)給手機(jī)無限發(fā)送各種網(wǎng)站的注冊驗(yàn)證碼短信的方法。
1.2 公司帶來的損失
短信一條5分錢,如果被大盜刷大家自己計(jì)算郵箱通知不用錢,但被大盜刷,帶寬、連接等都被占用,導(dǎo)致無法正常使用。
2.如何避免自己的網(wǎng)站成為"肉雞“或者被刷呢
-
增加圖形驗(yàn)證碼(開發(fā)人員)
圖形驗(yàn)證碼(CAPTCHA),是Completely Automated Public Turing Test to Tell Computers and Humans Apart (全自動(dòng)區(qū)分計(jì)算機(jī)和人類的圖靈測試)的簡稱。其本質(zhì)是-種區(qū)分用戶是計(jì)算機(jī)還是人的公共全自動(dòng)程序??梢杂行У姆乐鼓承┨囟ǔ绦蛞员┝Ψ绞讲粩噙M(jìn)行登錄嘗試。驗(yàn)證碼的不斷發(fā)展其實(shí)是隨著其破解功能的逐步強(qiáng)大而跟著演進(jìn)的,這是一個(gè)攻防博弈的精彩世界。
-
單IP請求次數(shù)限制(開發(fā)人員)
-
限制號碼發(fā)送(一般短信提供商會做)
-
攻防永遠(yuǎn)是有的,只過加大了攻擊者的成本,ROI劃不過來自然就放棄了
3.表單重復(fù)提交問題
- 用戶正常提交,由于網(wǎng)絡(luò)延遲等原因,未收到服務(wù)器的響應(yīng),這時(shí),用戶著急多點(diǎn)了幾次提交操作,會造成表單重復(fù)提交。
- 用戶正常提交,服務(wù)器也沒有延遲,但是提交完成后,用戶回退瀏覽器。重新提交,也會造成表單重復(fù)提交。
這兩個(gè)表單重復(fù)提交問題的解決方案:驗(yàn)證碼
4.Kaptcha框架介紹
谷歌開源的一個(gè)可高度配置的實(shí)用驗(yàn)證碼生成工具:
- 驗(yàn)證碼的字體/大小顏色
- 驗(yàn)證碼內(nèi)容的范圍(數(shù)字,字母,中文漢字!)
- 驗(yàn)證碼圖片的大小,邊框,邊框粗細(xì),邊框顏色
- 驗(yàn)證碼的干擾線驗(yàn)證碼的樣式(魚眼樣式、3D、 普通模糊)
5.前后端分離驗(yàn)證碼實(shí)戰(zhàn)開發(fā)-思路分析
這里以修改密碼為例,規(guī)則是修改用戶密碼必須一同將驗(yàn)證碼傳給后端,如果驗(yàn)證碼失效或者錯(cuò)誤都需要重新生成并獲取驗(yàn)證碼。
-
前端向后端請求驗(yàn)證碼。
-
后端通過谷歌開源工具Kaptcha生成圖形驗(yàn)證碼(實(shí)際是5個(gè)隨機(jī)字符),緩存到redis,key鍵可以是 業(yè)務(wù)+用戶id,value值就是那5個(gè)隨機(jī)字符。設(shè)置TTL為2分鐘。
-
后端將圖形驗(yàn)證碼轉(zhuǎn)base64編碼字符串,將該字符串返回給前端。
-
前端解析base64編碼的字符串,即可在頁面上顯示圖形驗(yàn)證碼。
-
用戶輸入密碼與驗(yàn)證碼后提交表單到后端。
-
后端根據(jù)業(yè)務(wù)和用戶id組成的key鍵到redis查找緩存的驗(yàn)證碼信息,會有如下情況:
-
如果redis返回為空,則通知前端驗(yàn)證碼失效,需要重新獲取驗(yàn)證碼。
-
如果redis返回不為空,但是不相等,說明驗(yàn)證碼輸入錯(cuò)誤。則刪除redis中對應(yīng)驗(yàn)證碼緩存,通知前端驗(yàn)證碼錯(cuò)誤,需要重新獲取驗(yàn)證碼。
-
如果redis返回不為空,并且相等,則校驗(yàn)成功,就刪除redis中對應(yīng)驗(yàn)證碼緩存,并在mysql中修改密碼。最后通知前端修改成功。
-
6.前后端分離驗(yàn)證碼實(shí)戰(zhàn)開發(fā)-后端代碼
5.1 pom.xml核心依賴
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
5.2 KaptchaConfig配置類
package com.zhulang.waveedu.sms.config;
import com.google.code.kaptcha.Producer;
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;
/**
* 谷歌提供的圖片驗(yàn)證碼kaptcha
*
* @author 狐貍半面添
* @create 2023-01-29 11:02
*/
@Configuration
public class KaptchaConfig {
@Bean
public Producer kaptcha() {
Properties properties = new Properties();
/*
設(shè)置圖片有邊框并且為藍(lán)色
properties.setProperty("kaptcha.border", "no");
properties.setProperty("kaptcha.border.color", "blue");
*/
// 設(shè)置圖片無邊框
properties.setProperty("kaptcha.border", "no");
// 背景顏色漸變開始,這里設(shè)置的是rgb值156,156,156
properties.put("kaptcha.background.clear.from", "156,156,156");
// 背景顏色漸變結(jié)束,這里設(shè)置以白色結(jié)束
properties.put("kaptcha.background.clear.to", "white");
// 字體顏色,這里設(shè)置為黑色
properties.put("kaptcha.textproducer.font.color", "black");
// 文字間隔,這里設(shè)置為10px
properties.put("kaptcha.textproducer.char.space", "10");
/*
如果需要去掉干擾線,則如此配置:
properties.put("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");
*/
// 干擾線顏色配置,這里設(shè)置成了idea的Darcula主題的背景色
properties.put("kaptcha.noise.color", "43,43,43");
// 字體
properties.put("kaptcha.textproducer.font.names", "宋體,楷體,微軟雅黑");
// 圖片寬度,默認(rèn)也是200px
properties.setProperty("kaptcha.image.width", "200");
// 圖片高度,默認(rèn)也是50px
properties.setProperty("kaptcha.image.height", "50");
// 從哪些字符中產(chǎn)生
properties.setProperty("kaptcha.textproducer.char.string", "0123456789abcdefghijklmnopqrsduvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
// 字符個(gè)數(shù)
properties.setProperty("kaptcha.textproducer.char.length", "5");
Config config = new Config(properties);
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
5.3 CaptchaServiceController生成與獲取驗(yàn)證碼
- RedisCacheUtils是一個(gè)自定義的redis緩存工具類
- RedisConstants是一個(gè)redis常量類,主要是設(shè)置緩存key前綴與TTL
- UserHolderUtils.getUserId():通過自定義UserHolderUtils工具類獲取用戶id(通過token從redis獲取用戶信息,包括了userId)
package com.zhulang.waveedu.sms.controller;
import com.google.code.kaptcha.Producer;
import com.zhulang.waveedu.common.constant.RedisConstants;
import com.zhulang.waveedu.common.entity.Result;
import com.zhulang.waveedu.common.util.RedisCacheUtils;
import com.zhulang.waveedu.common.util.UserHolderUtils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
/**
* 生成圖片驗(yàn)證碼
*
* @author 狐貍半面添
* @create 2023-01-29 17:35
*/
@RestController
@RequestMapping("/captcha")
public class CaptchaServiceController {
@Resource
private Producer producer;
@Resource
private RedisCacheUtils redisCacheUtils;
/**
* 獲取修改密碼的圖片驗(yàn)證碼base64編碼
* 并緩存至redis中
*
* @return base編碼
*/
@GetMapping("/pwd/vc.jpg")
public Result getPwdCaptcha(){
// 1.生成驗(yàn)證碼字符
String text = producer.createText();
// 2.生成圖片
BufferedImage bi = producer.createImage(text);
FastByteArrayOutputStream fos = new FastByteArrayOutputStream();
try {
ImageIO.write(bi, "jpg", fos);
// 3.緩存至 redis 中
redisCacheUtils.setCacheObject(RedisConstants.PWD_CODE_KEY+ UserHolderUtils.getUserId(),text,RedisConstants.PWD_CODE_TTL);
// 4.返回驗(yàn)證碼圖片的base64編碼
String imgEncode = Base64.encodeBase64String(fos.toByteArray());
fos.flush();
return Result.ok(imgEncode);
}catch (Exception e){
return Result.error();
}finally {
fos.close();
}
}
}
5.4 修改密碼驗(yàn)證碼校驗(yàn)代碼
package com.zhulang.waveedu.basic.vo;
import com.zhulang.waveedu.common.util.RegexUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
/**
* 修改密碼時(shí)的VO
*
* @author 狐貍半面添
* @create 2023-01-29 22:09
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UpdatePwdVO {
/**
* 第一次輸入的密碼
*/
@NotBlank(message = "輸入密碼為空")
@Pattern(regexp = RegexUtils.RegexPatterns.PASSWORD_REGEX, message = "密碼格式錯(cuò)誤,應(yīng)為8-16位的數(shù)字或字母")
private String firPassword;
/**
* 第二次輸入的密碼(確認(rèn)密碼)
*/
@NotBlank(message = "輸入密碼為空")
@Pattern(regexp = RegexUtils.RegexPatterns.PASSWORD_REGEX, message = "密碼格式錯(cuò)誤,應(yīng)為8-16位的數(shù)字或字母")
private String secPassword;
/**
* 圖片驗(yàn)證碼字符
*/
@NotBlank(message = "驗(yàn)證碼為空")
@Pattern(regexp = RegexUtils.RegexPatterns.CAPTCHA_CODE_REGEX, message = "驗(yàn)證碼格式錯(cuò)誤")
private String code;
}
/**
* User登錄,注冊,推出登錄,注銷的控制器
*
* @author 狐貍半面添
* @create 2023-01-17 23:14
*/
@RestController
@RequestMapping("/user")
public class UserController {
/**
* 修改密碼
*
* @param updatePwdVO 兩個(gè)密碼+code
* @return 修改情況
*/
@PutMapping("/updatePwd")
public Result updatePwd(@Validated @RequestBody UpdatePwdVO updatePwdVO){
return userService.updatePwd(updatePwdVO);
}
}
/**
* ServiceImpl實(shí)現(xiàn)了IService,提供了IService中基礎(chǔ)功能的實(shí)現(xiàn)
* 若ServiceImpl無法滿足業(yè)務(wù)需求,則可以使用自定的UserService定義方法,并在實(shí)現(xiàn)類中實(shí)現(xiàn)
*
* @author 狐貍半面添
* @create 2023-01-17 23:31
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Resource
private RedisCacheUtils redisCacheUtils;
@Override
public Result updatePwd(UpdatePwdVO updatePwdVO) {
// 1.如果兩個(gè)密碼不一致,則返回error
if (!updatePwdVO.getFirPassword().equals(updatePwdVO.getSecPassword())){
return Result.error(HttpStatus.HTTP_VERIFY_FAIL.getCode(), "兩次密碼不一致");
}
// 2.校驗(yàn)驗(yàn)證碼是否正確
String code = redisCacheUtils.getCacheObject(RedisConstants.PWD_CODE_KEY + UserHolderUtils.getUserId());
// 2.1 不存在則返回
if (code==null){
return Result.error(HttpStatus.HTTP_VERIFY_FAIL.getCode(),"驗(yàn)證碼已失效,請重新獲取");
}
// 2.2 存在但不一致則清除并返回(忽略大小寫)
if (!code.equalsIgnoreCase(updatePwdVO.getCode())){
redisCacheUtils.deleteObject(RedisConstants.PWD_CODE_KEY + UserHolderUtils.getUserId());
return Result.error(HttpStatus.HTTP_VERIFY_FAIL.getCode(),"驗(yàn)證碼錯(cuò)誤,請重新獲取");
}
// 3.驗(yàn)證碼正確,則從緩存中移除
redisCacheUtils.deleteObject(RedisConstants.PWD_CODE_KEY + UserHolderUtils.getUserId());
// 4.修改密碼
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getId,UserHolderUtils.getUserId())
.set(User::getPassword,PasswordEncoderUtils.encode(updatePwdVO.getFirPassword()));
this.update(wrapper);
// 5.返回
return Result.ok();
}
}
5.5 測試圖片驗(yàn)證碼生成
我們對這段Base64進(jìn)行解碼,這里就使用一個(gè)在線工具:base64圖片在線轉(zhuǎn)換工具 - 站長工具 (chinaz.com)
?? 注意需要在上圖中得到的字符串前面指定轉(zhuǎn)換格式:data:image/jpg;base64,
7.驗(yàn)證碼未來發(fā)展的討論
驗(yàn)證碼技術(shù)其實(shí)是一個(gè)攻防博弈的動(dòng)態(tài)發(fā)展的技術(shù),因此,隨著驗(yàn)證碼發(fā)展得越來越安全,相應(yīng)的破解技術(shù)也跟著不斷發(fā)展,隨之而來的,是雙方的
資源成本越來越高。
例如對于互聯(lián)網(wǎng)應(yīng)用來說,商業(yè)驗(yàn)證碼、短信驗(yàn)證碼等這些驗(yàn)證碼,如果應(yīng)用的業(yè)務(wù)流程控制不好,很容易被羊毛黨、黑客等人利用,造成成本極大浪費(fèi)。而網(wǎng)上逐漸出現(xiàn)的各種奇葩驗(yàn)證碼,人眼難辨,也讓很多正常用戶叫苦不迭。
對于破解方來說,隨著機(jī)器學(xué)習(xí)以及人工打碼等技術(shù)的不斷發(fā)展,可選擇的技術(shù)手段也越來越多。驗(yàn)證碼技術(shù),也在破解方的大量資源投入下,變得越來越雞肋。并且,很多互聯(lián)網(wǎng)應(yīng)用逐漸復(fù)雜的驗(yàn)證方式,安全性提高的同時(shí),也提高了很多正常用戶的使用門檻,成為了各種電信詐騙的溫床。驗(yàn)證碼技術(shù)逐漸開始偏離了最初的初衷。
而未來的驗(yàn)證碼技術(shù),一方面,會通過引入更多的驗(yàn)證元素來進(jìn)一步提高驗(yàn)證碼的安全性,例如刷臉、刷指紋、語音交互、點(diǎn)選你購買過的商品等。另一方面,通過對行為式驗(yàn)證碼的研究逐漸深入,可以從傳統(tǒng)的面向結(jié)果的驗(yàn)證,轉(zhuǎn)化成面向過程的行為驗(yàn)證,并逐漸減少人工參與,降低關(guān)鍵信息被劫持的風(fēng)險(xiǎn),形成更多對用戶無感知的驗(yàn)證方式。例如分析用戶鼠標(biāo)軌跡、按鍵頻率、使用習(xí)慣等。文章來源:http://www.zghlxwxcb.cn/news/detail-806043.html
總之,驗(yàn)證碼技術(shù),是一個(gè)所有人都將親身參與的精彩世界。文章來源地址http://www.zghlxwxcb.cn/news/detail-806043.html
到了這里,關(guān)于前后端分離java開發(fā)圖形驗(yàn)證碼+谷歌開源Kaptcha使用(Springboot+redis實(shí)現(xiàn)圖形驗(yàn)證碼校驗(yàn))的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!