SpringBoot整合腾讯云新一代行为验证码
一 产品介绍
腾讯云官方介绍链接
腾讯云新一代行为验证码(Captcha),基于十道安全防护策略,为网页、App、小程序开发者打造立体、全面的人机验证。在保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。
验证码服务可以帮助您解决以下业务安全问题:
- 登录注册:有效防止撞库攻击、阻止注册机批量注册小号。
- 活动秒杀:有效拦截刷单操作,防止自动机刷取福利券。
- 点赞发帖:有效解决广告屠版、恶意灌水、刷票问题。
- 数据保护:有效防止自动机、爬虫盗取网页内容和数据。
操作简单,部分示例:
二 大致流程
这个流程图可以点击链接看到 验证码 快速入门_腾讯云
大致流程就是:
- 前端请求appid
- 拿到appid后使用腾讯云提供的方法,可以加载出来各种类型的验证码(验证码的类型,看appid申请的是什么样的)
- 用户完成认证后,会得到返回一系列参数,如randstr,ticket
- 前端拿着randstr,ticket请求后端接口,验证这个验证码是否OK,后端返回true或者false
三 腾讯云申请流程
1. 在腾讯云官网搜索验证码
2. 领取7天免费的,没用过验证码功能的第一次进去会看到领取的地方
3. 前往控制台
4. 记录下来生成的密钥
5 记录下来自己的账号密钥
四 后端代码编写
1. 总的demo结构
2. pom
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 腾讯云 验证码 --><dependency><groupId>com.tencentcloudapi</groupId><artifactId>tencentcloud-sdk-java</artifactId><version>3.1.1297</version></dependency><!-- Lombok for reducing boilerplate code --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- Validation --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>
3. Yaml
server:port: 8080spring:application:name: tcaptcha-demo# 日志配置logging:level:org.example: DEBUGcom.tencentcloudapi: INFOpattern:console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"# 腾讯云验证码配置
tencent:captcha:# 腾讯云API密钥IDsecretId: xxxxxxxxxxxx# 腾讯云API密钥secretKey: xxxxxxxxxx# 验证码应用IDcaptchaAppId: 123456789# 验证码应用密钥appSecretKey: xxxxxxxx# API端点endpoint: xxxxxxxxxxxx# API服务名称service: captcha# API操作名称action: DescribeCaptchaResult# 验证码类型captchaType: 9# 是否需要获取验证码时间needGetCaptchaTime: 1# API版本version: "2025-07-10"
4. 配置类 config包下
4.1
package org.example.config;import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** 验证码配置类** @author wangmh**/
@Configuration
@EnableConfigurationProperties(CaptchaProperties.class)
public class CaptchaConfig {/*** 配置ObjectMapper* * @return ObjectMapper实例*/@Beanpublic ObjectMapper objectMapper() {ObjectMapper objectMapper = new ObjectMapper();objectMapper.registerModule(new JavaTimeModule());objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);return objectMapper;}
}
package org.example.config;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;/*** 腾讯云验证码配置属性** @author wangmh**/
@Component
@ConfigurationProperties(prefix = "tencent.captcha")
public class CaptchaProperties {/*** 腾讯云API密钥ID*/private String secretId;/*** 腾讯云API密钥*/private String secretKey;/*** 验证码应用ID*/private Integer captchaAppId;/*** 验证码类型*/private Integer captchaType;/*** 是否需要获取验证码时间*/private Integer needGetCaptchaTime;/*** 应用密钥*/private String appSecretKey;/*** API版本*/private String version;/*** API端点*/private String endpoint;/*** API服务名称*/private String service;/*** API操作名称*/private String action;// Getters and Setterspublic String getSecretId() {return secretId;}public void setSecretId(String secretId) {this.secretId = secretId;}public String getSecretKey() {return secretKey;}public void setSecretKey(String secretKey) {this.secretKey = secretKey;}public Integer getCaptchaAppId() {return captchaAppId;}public void setCaptchaAppId(Integer captchaAppId) {this.captchaAppId = captchaAppId;}public Integer getCaptchaType() {return captchaType;}public void setCaptchaType(Integer captchaType) {this.captchaType = captchaType;}public Integer getNeedGetCaptchaTime() {return needGetCaptchaTime;}public void setNeedGetCaptchaTime(Integer needGetCaptchaTime) {this.needGetCaptchaTime = needGetCaptchaTime;}public String getAppSecretKey() {return appSecretKey;}public void setAppSecretKey(String appSecretKey) {this.appSecretKey = appSecretKey;}public String getVersion() {return version;}public void setVersion(String version) {this.version = version;}public String getEndpoint() {return endpoint;}public void setEndpoint(String endpoint) {this.endpoint = endpoint;}public String getService() {return service;}public void setService(String service) {this.service = service;}public String getAction() {return action;}public void setAction(String action) {this.action = action;}
}
5 实体类 dto包下
package org.example.dto;import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;/*** 验证码验证请求DTO* * @author wangmh**/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CaptchaVerifyRequest {/*** 用户IP地址*/@NotBlank(message = "用户IP地址不能为空")@JsonProperty("userIp")private String userIp;/*** 随机字符串*/@NotBlank(message = "随机字符串不能为空")@JsonProperty("randstr")private String randstr;/*** 验证票据*/@NotBlank(message = "验证票据不能为空")@JsonProperty("ticket")private String ticket;
}
package org.example.dto;import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;/*** 验证码验证响应DTO** @author wangmh**/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CaptchaVerifyResponse {/*** 请求ID*/@JsonProperty("RequestId")private String RequestId;/*** 验证码验证结果* 1: 验证通过*/@JsonProperty("CaptchaCode")private Integer CaptchaCode;/*** 验证码验证消息*/@JsonProperty("CaptchaMsg")private String CaptchaMsg;/*** 前端获取验证码时间*/@JsonProperty("GetCaptchaTime")private Integer GetCaptchaTime;/*** 提交验证码时间*/@JsonProperty("SubmitCaptchaTime")private Integer SubmitCaptchaTime;/*** 无感验证模式下,该参数返回验证结果*/@JsonProperty("EvilLevel")private Integer EvilLevel;/*** 拦截类型*/@JsonProperty("EvilBitmap")private Integer EvilBitmap;/*** 是否验证通过*/public boolean isSuccess() {return CaptchaCode != null && CaptchaCode == 1;}}
6 Service
package org.example.service;import com.fasterxml.jackson.databind.ObjectMapper;
import com.tencentcloudapi.common.CommonClient;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.config.CaptchaProperties;
import org.example.dto.CaptchaVerifyRequest;
import org.example.dto.CaptchaVerifyResponse;
import org.springframework.stereotype.Service;import jakarta.annotation.PostConstruct;
import org.springframework.util.ObjectUtils;import java.util.HashMap;
import java.util.Map;/*** 腾讯云验证码服务** @author wangmh**/
@Slf4j
@Service
@RequiredArgsConstructor
public class CaptchaService {private final CaptchaProperties captchaProperties;private final ObjectMapper objectMapper;private CommonClient commonClient;/*** 初始化腾讯云客户端*/@PostConstructpublic void init() {try {Credential credential = new Credential(captchaProperties.getSecretId(), captchaProperties.getSecretKey());HttpProfile httpProfile = new HttpProfile();httpProfile.setEndpoint(captchaProperties.getEndpoint());ClientProfile clientProfile = new ClientProfile();clientProfile.setHttpProfile(httpProfile);this.commonClient = new CommonClient(captchaProperties.getService(),captchaProperties.getVersion(),credential,"",clientProfile);log.info("腾讯云验证码客户端初始化成功");} catch (Exception e) {log.error("腾讯云验证码客户端初始化失败", e);throw new RuntimeException("腾讯云验证码客户端初始化失败", e);}}/*** 验证验证码* * @param request 验证请求* @return 验证响应*/public CaptchaVerifyResponse verifyCaptcha(CaptchaVerifyRequest request) throws TencentCloudSDKException {try {Map<String, Object> params = buildRequestParams(request);String paramsJson = objectMapper.writeValueAsString(params);log.info("验证码请求参数: {}", paramsJson);String response = commonClient.call(captchaProperties.getAction(), paramsJson);log.info("腾讯云API响应: {}", response);Map<String, Object> responseMap = objectMapper.readValue(response, Map.class);CaptchaVerifyResponse captchaResponse = buildCaptchaResponse(responseMap);return captchaResponse;} catch (TencentCloudSDKException e) {log.error("腾讯云API调用失败", e);throw e;} catch (Exception e) {log.error("验证码验证过程中发生异常", e);throw new RuntimeException("验证码验证失败", e);}}/*** 构建请求参数* * @param request 验证请求* @return 请求参数Map*/private Map<String, Object> buildRequestParams(CaptchaVerifyRequest request) {Map<String, Object> params = new HashMap<>();// 从配置中获取的参数params.put("CaptchaAppId", captchaProperties.getCaptchaAppId());params.put("CaptchaType", captchaProperties.getCaptchaType());params.put("NeedGetCaptchaTime", captchaProperties.getNeedGetCaptchaTime());params.put("AppSecretKey", captchaProperties.getAppSecretKey());// 从请求中获取的参数params.put("UserIp", request.getUserIp());params.put("Randstr", request.getRandstr());params.put("Ticket", request.getTicket());return params;}/*** 简单验证方法 - 只返回是否验证通过* * @param userIp 用户IP* @param randstr 随机字符串* @param ticket 验证票据* @return 是否验证通过*/public boolean isCaptchaValid(String userIp, String randstr, String ticket) {try {CaptchaVerifyRequest request = CaptchaVerifyRequest.builder().userIp(userIp).randstr(randstr).ticket(ticket).build();CaptchaVerifyResponse response = verifyCaptcha(request);return response.isSuccess();} catch (Exception e) {log.error("验证码验证失败", e);return false;}}/*** 构建验证码响应对象** @param responseMap 响应Map* @return 验证码响应对象*/private CaptchaVerifyResponse buildCaptchaResponse(Map<String, Object> responseMap) {CaptchaVerifyResponse response = new CaptchaVerifyResponse();try {if (responseMap.containsKey("Response")) {Map<String, Object> responseData = (Map<String, Object>) responseMap.get("Response");populateResponse(response, responseData);}} catch (Exception e) {log.error("解析响应数据失败", e);}return response;}/*** 填充响应数据** @param response 响应对象* @param data 数据Map*/private void populateResponse(CaptchaVerifyResponse response, Map<String, Object> data) {if (data.containsKey("RequestId")) {response.setRequestId((String) data.get("RequestId"));}if (data.containsKey("CaptchaCode")) {Object v = data.get("CaptchaCode");if (!ObjectUtils.isEmpty(v))response.setCaptchaCode(v instanceof Integer ? (Integer) v : Integer.parseInt(v.toString()));}if (data.containsKey("CaptchaMsg")) {response.setCaptchaMsg((String) data.get("CaptchaMsg"));}if (data.containsKey("GetCaptchaTime")) {Object v = data.get("GetCaptchaTime");if (!ObjectUtils.isEmpty(v))response.setGetCaptchaTime(v instanceof Integer ? (Integer) v : Integer.parseInt(v.toString()));}if (data.containsKey("SubmitCaptchaTime")) {Object v = data.get("SubmitCaptchaTime");if (!ObjectUtils.isEmpty(v))response.setSubmitCaptchaTime(v instanceof Integer ? (Integer) v : Integer.parseInt(v.toString()));}if (data.containsKey("EvilLevel")) {Object v = data.get("EvilLevel");if (!ObjectUtils.isEmpty(v))response.setEvilLevel(v instanceof Integer ? (Integer) v : Integer.parseInt(v.toString()));}if (data.containsKey("EvilBitmap")) {Object v = data.get("EvilBitmap");if (!ObjectUtils.isEmpty(v))response.setEvilBitmap(v instanceof Integer ? (Integer) v : Integer.parseInt(v.toString()));}}}
6. Controller
package org.example.controller;import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.config.CaptchaProperties;
import org.example.dto.CaptchaVerifyRequest;
import org.example.dto.CaptchaVerifyResponse;
import org.example.service.CaptchaService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;import jakarta.validation.Valid;/*** 验证码控制器** @author wangmh**/
@Slf4j
@RestController
@RequestMapping("/api/captcha")
@RequiredArgsConstructor
public class CaptchaController {private final CaptchaService captchaService;private final CaptchaProperties captchaProperties;/*** 验证验证码* * @param request 验证请求* @return 验证结果*/@PostMapping("/verify")public ResponseEntity<CaptchaVerifyResponse> verifyCaptcha(@Valid @RequestBody CaptchaVerifyRequest request) {try {log.info("收到简单验证码验证请求: userIp={}, randstr={}", request.getUserIp(), request.getRandstr());CaptchaVerifyResponse response = captchaService.verifyCaptcha(request);return ResponseEntity.ok(response);} catch (Exception e) {log.error("验证码验证失败", e);return ResponseEntity.internalServerError().build();}}/*** 简单验证接口* * @param userIp 用户IP* @param randstr 随机字符串* @param ticket 验证票据* @return 是否验证通过*/@GetMapping("/verify")public ResponseEntity<Boolean> verifyCaptchaSimple(@RequestParam String userIp,@RequestParam String randstr,@RequestParam String ticket) {try {log.info("收到简单验证码验证请求: userIp={}, randstr={}", userIp, randstr);boolean isValid = captchaService.isCaptchaValid(userIp, randstr, ticket);log.info("收到简单验证码验证请求: userIp={}, randstr={} 验证结果:{}", userIp, randstr, isValid);return ResponseEntity.ok(isValid);} catch (Exception e) {log.error("验证码验证失败", e);return ResponseEntity.internalServerError().build();}}/*** 获取验证码应用ID* @return captchaAppId*/@GetMapping("/appid")public ResponseEntity<Integer> getCaptchaAppId() {return ResponseEntity.ok(captchaProperties.getCaptchaAppId());}}
7. 请求测试
五 前端代码
里面的js 下载地址:https://turing.captcha.qcloud.com/TJCaptcha.js
css 可以删掉
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Web 前端接入示例</title><link rel="stylesheet" href="./css/style.css"><!-- 验证码程序依赖(必须)。请勿修改以下程序依赖,如通过其他手段规避加载,会导致验证码无法正常更新,对抗能力无法保证,甚至引起误拦截。 --><script src="./js/TJCaptcha.js"></script>
</head><body><button id="CaptchaId" type="button">验证</button>
</body><script>// 定义回调函数function callback(res) {// 第一个参数传入回调结果,结果如下:// ret Int 验证结果,0:验证成功。2:用户主动关闭验证码。// ticket String 验证成功的票据,当且仅当 ret = 0 时 ticket 有值。// CaptchaAppId String 验证码应用ID。// bizState Any 自定义透传参数。// randstr String 本次验证的随机串,后续票据校验时需传递该参数。// verifyDuration Int 验证码校验接口耗时(ms)。// actionDuration Int 操作校验成功耗时(用户动作+校验完成)(ms)。// sid String 链路sid。console.log('callback:', res);// res(用户主动关闭验证码)= {ret: 2, ticket: null}// res(验证成功) = {ret: 0, ticket: "String", randstr: "String"}// res(请求验证码发生错误,验证码自动返回trerror_前缀的容灾票据) = {ret: 0, ticket: "String", randstr: "String", errorCode: Number, errorMessage: "String"}// 此处代码仅为验证结果的展示示例,真实业务接入,建议基于ticket和errorCode情况做不同的业务处理if (res.ret === 0) {// 复制结果至剪切板var str = '【randstr】->【' + res.randstr + '】 【ticket】->【' + res.ticket + '】';var ipt = document.createElement('input');ipt.value = str;document.body.appendChild(ipt);ipt.select();document.execCommand("Copy");document.body.removeChild(ipt);alert('1. 返回结果(randstr、ticket)已复制到剪切板,ctrl+v 查看。2. 打开浏览器控制台,查看完整返回结果。');}}// 定义验证码js加载错误处理函数function loadErrorCallback() {var appid = '您的CaptchaAppId';// 生成容灾票据或自行做其它处理var ticket = 'trerror_1001_' + appid + '_' + Math.floor(new Date().getTime() / 1000);callback({ret: 0,randstr: '@' + Math.random().toString(36).substr(2),ticket: ticket,errorCode: 1001,errorMessage: 'jsload_error'});}// 定义验证码触发事件window.onload = function () {document.getElementById('CaptchaId').onclick = function () {try {// 生成一个验证码对象// CaptchaAppId:登录验证码控制台,从【验证管理】页面进行查看。如果未创建过验证,请先新建验证。注意:不可使用客户端类型为小程序的CaptchaAppId,会导致数据统计错误。//callback:定义的回调函数var captcha = new TencentCaptcha('198595300', callback, {userLanguage: 'zh-cn',showFn: (ret) => {const {duration, // 验证码渲染完成的耗时(ms)sid, // 链路sid} = ret;},});// 调用方法,显示验证码captcha.show();} catch (error) {// 加载异常,调用验证码js加载错误处理函数loadErrorCallback();}}}
</script></html>
六 官方文档
验证码产品页面:T-sec-腾讯安全天御-行为式验证码 Captcha-腾讯云
产品叙述:验证码 产品概述_腾讯云
web端接入:验证码 Web 客户端接入_腾讯云
接收票据校验:验证码 接入票据校验(Web 及 App)_腾讯云
核验验证码票据结果:验证码 核查验证码票据结果(Web及APP)_腾讯云
jar包引入:云产品SDK中心_云产品SDK文档-腾讯云