1.導入依賴:
<dependency><groupId>cloud.tianai.captcha</groupId><artifactId>tianai-captcha-springboot-starter</artifactId><version>1.5.2</version>
</dependency>
2.在application.yml中配置驗證碼相關配置:
# 滑塊驗證碼配置, 詳細請看 cloud.tianai.captcha.autoconfiguration.ImageCaptchaProperties 類
captcha:# 如果項目中使用到了redis,滑塊驗證碼會自動把驗證碼數據存到redis中, 這里配置redis的key的前綴,默認是captcha:sliderprefix: captcha# 驗證碼過期時間,默認是2分鐘,單位毫秒, 可以根據自身業務進行調整expire:# 默認緩存時間 2分鐘default: 10000# 針對 點選驗證碼 過期時間設置為 2分鐘, 因為點選驗證碼驗證比較慢,把過期時間調整大一些WORD_IMAGE_CLICK: 20000# 使用加載系統自帶的資源, 默認是 false(這里系統的默認資源包含 滑動驗證碼模板/旋轉驗證碼模板,如果想使用系統的模板,這里設置為true)init-default-resource: true# 緩存控制, 默認為false不開啟local-cache-enabled: false# 緩存開啟后,驗證碼會提前緩存一些生成好的驗證數據, 默認是20local-cache-size: 20# 緩存開啟后,緩存拉取失敗后等待時間 默認是 5秒鐘local-cache-wait-time: 5000# 緩存開啟后,緩存檢查間隔 默認是2秒鐘local-cache-period: 2000# 配置字體包,供文字點選驗證碼使用,可以配置多個,不配置使用默認的字體font-path:- classpath:font/SimHei.ttfsecondary:# 二次驗證, 默認false 不開啟enabled: false# 二次驗證過期時間, 默認 2分鐘expire: 120000# 二次驗證緩存key前綴,默認是 captcha:secondarykeyPrefix: "captcha:secondary"
3.接入springboot進行驗證碼的開發:
controller:
@RestController
@RequestMapping("/system/captcha")
public class CaptchaController {@Resourceprivate CaptchaService captchaService;@GetMapping("/get-slider-image")@ApiOperation("生成滑塊驗證碼圖片")public CommonResult<CaptchaResponse<ImageCaptchaVO>> getSliderCaptchaImage() {return success(captchaService.getSliderCaptchaImage());}@PostMapping("/check")@ApiOperation("滑塊驗證碼確認")public CommonResult<Boolean> checkCaptchaImage(HttpServletRequest request,@Valid @RequestBody CaptchaImageVo captchaImageVo) {return success(captchaService.checkCaptchaImage(captchaImageVo));}}
service:
/*** 驗證碼 Service 接口*/
public interface CaptchaService {/*** 是否開啟圖片驗證碼** @return 是否*/Boolean isCaptchaEnable();/*** 獲得 uuid 對應的驗證碼** @param uuid 驗證碼編號* @return 驗證碼*/String getCaptchaCode(String uuid);/*** 刪除 uuid 對應的驗證碼** @param uuid 驗證碼編號*/void deleteCaptchaCode(String uuid);/*** 圖片驗證碼驗證**/Boolean checkCaptchaImage(CaptchaImageVo captchaImageVo);/*** 獲得驗證碼圖片** @return 驗證碼圖片*/CaptchaResponse<ImageCaptchaVO> getSliderCaptchaImage();/*** 判斷對應的滑動驗證碼是否通過**/Boolean alreadyValid(String uuid) ;
}
impl:
/*** 驗證碼 Service 實現類*/
@Service
@Slf4j
public class CaptchaServiceImpl implements CaptchaService {@Value("${captcha.expire.defult}")private Duration timeout;@Resourceprivate MyResourceStoreProperties myResourceStoreProperties;@Resourceprivate CaptchaRedisDAO captchaRedisDAO;@Resourceprivate ImageCaptchaApplication application;@Resourceprivate CacheStore cacheStore;@Resourceprivate ImageCaptchaProperties imageCaptchaProperties;@Overridepublic String getCaptchaCode(String uuid) {return captchaRedisDAO.get(uuid);}@Overridepublic void deleteCaptchaCode(String uuid) {captchaRedisDAO.delete(uuid);}@Overridepublic Boolean checkCaptchaImage(CaptchaImageVo captchaImageVo) {Boolean isPass = application.matching(captchaImageVo.getId(), captchaImageVo.getImageCaptchaTrack());captchaRedisDAO.set(captchaImageVo.getId(), isPass.toString(), timeout);return isPass;}@Overridepublic Boolean alreadyValid(String uuid) {if (captchaRedisDAO.get(uuid) != null) {boolean result = Boolean.parseBoolean(captchaRedisDAO.get(uuid));captchaRedisDAO.delete(uuid);return result;}return false;}@Overridepublic CaptchaResponse<ImageCaptchaVO> getSliderCaptchaImage() {//加載模板myResourceStoreProperties.MyResourceStore();CaptchaResponse<ImageCaptchaVO> response = application.generateCaptcha(CaptchaTypeConstant.SLIDER);Map<String, Object> data = cacheStore.getCache(imageCaptchaProperties.getPrefix().concat(":").concat(response.getId()));//動態設置偏移容錯data.put("tolerant", 0.2);cacheStore.setCache(imageCaptchaProperties.getPrefix().concat(":").concat(response.getId()), data, 20000L, TimeUnit.MILLISECONDS);return response;}}
注入的自定義類:
@Component
public class MyResourceStoreProperties extends DefaultResourceStore {public void MyResourceStore() {// 滑塊驗證碼 模板 (系統內置)Map<String, Resource> template1 = new HashMap<>(4);template1.put(SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/active.png")));template1.put(SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/fixed.png")));template1.put(SliderCaptchaConstant.TEMPLATE_MATRIX_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/matrix.png")));Map<String, Resource> template2 = new HashMap<>(4);template2.put(SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/active.png")));template2.put(SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/fixed.png")));template2.put(SliderCaptchaConstant.TEMPLATE_MATRIX_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/matrix.png")));// 1. 添加一些模板addTemplate(CaptchaTypeConstant.SLIDER, template1);addTemplate(CaptchaTypeConstant.SLIDER, template2);// 2. 添加自定義背景圖片int dayOfWeek=DateUtil.dayOfWeek(new Date())-1;addResource(CaptchaTypeConstant.SLIDER, new Resource("classpath", "bgimages/slider.jpg"));//addResource(CaptchaTypeConstant.SLIDER, new Resource("URL", "https://soarway-fangpiao-backup-dev.oss-cn-hangzhou.aliyuncs.com/slider-"+dayOfWeek+".jpg"));}}
@Repository
public class CaptchaRedisDAO {@Resourceprivate StringRedisTemplate stringRedisTemplate;public String get(String uuid) {String redisKey = formatKey(uuid);return stringRedisTemplate.opsForValue().get(redisKey);}public void set(String uuid, String code, Duration timeout) {String redisKey = formatKey(uuid);stringRedisTemplate.opsForValue().set(redisKey, code, timeout);}public void delete(String uuid) {String redisKey = formatKey(uuid);stringRedisTemplate.delete(redisKey);}private static String formatKey(String uuid) {return String.format(CAPTCHA_CODE.getKeyTemplate(), uuid);}}
?4.驗證碼的雙重驗證:
防止惡意劫持和重放攻擊
第一次校驗:
@Overridepublic Boolean checkCaptchaImage(CaptchaImageVo captchaImageVo) {Boolean isPass = application.matching(captchaImageVo.getId(), captchaImageVo.getImageCaptchaTrack());captchaRedisDAO.set(captchaImageVo.getId(), isPass.toString(), timeout);return isPass;}
? ? 用戶->>前端: ?1.拖動滑塊完成驗證
前端->>后端: 發送滑塊ID+軌跡數據 (checkCaptchaImage)
后端->>后端: 計算軌跡匹配度
后端->>Redis: 存儲驗證結果 (key:滑塊ID, value:true/false)
后端->>前端: 返回實時結果
第二次校驗:
@Overridepublic Boolean alreadyValid(String uuid) {if (captchaRedisDAO.get(uuid) != null) {boolean result = Boolean.parseBoolean(captchaRedisDAO.get(uuid));captchaRedisDAO.delete(uuid);return result;}return false;}
? ? 用戶->>前端: 2. 提交登錄表單
前端->>后端: 發送表單數據+滑塊ID (alreadyValid)
后端->>Redis: 讀取驗證結果
Redis->>后端: 返回預存結果
后端->>Redis: 刪除該滑塊ID的緩存
后端->>前端: 返回最終驗證結果
這樣即使黑客獲取了第一次驗證成功的請求,也無法重復使用該滑塊ID進行二次驗證,因為結果在 alreadyValid 調用后已被刪除。這種設計有效防止了重放攻擊,同時優化了系統性能