SpringBoot整合腾讯云新一代行为验证码

一 产品介绍

腾讯云官方介绍链接

腾讯云新一代行为验证码(Captcha),基于十道安全防护策略,为网页、App、小程序开发者打造立体、全面的人机验证。在保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。

验证码服务可以帮助您解决以下业务安全问题:

  1. 登录注册:有效防止撞库攻击、阻止注册机批量注册小号。
  2. 活动秒杀:有效拦截刷单操作,防止自动机刷取福利券。
  3. 点赞发帖:有效解决广告屠版、恶意灌水、刷票问题。
  4. 数据保护:有效防止自动机、爬虫盗取网页内容和数据。

操作简单,部分示例:

二 大致流程

这个流程图可以点击链接看到 验证码 快速入门_腾讯云

大致流程就是:

  • 前端请求appid
  • 拿到appid后使用腾讯云提供的方法,可以加载出来各种类型的验证码(验证码的类型,看appid申请的是什么样的)
  • 用户完成认证后,会得到返回一系列参数,如randstr,ticket
  • 前端拿着randstr,ticket请求后端接口,验证这个验证码是否OK,后端返回true或者false

三 腾讯云申请流程

  1. 在腾讯云官网搜索验证码
  1. 领取7天免费的,没用过验证码功能的第一次进去会看到领取的地方

  2. 前往控制台

  1. 记录下来生成的密钥

5 记录下来自己的账号密钥

四 后端代码编写

1. 总的demo结构

2. pom

XML 复制代码
    <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

javascript 复制代码
server:
  port: 8080

spring:
  application:
    name: tcaptcha-demo
  
  # 日志配置
  logging:
    level:
      org.example: DEBUG
      com.tencentcloudapi: INFO
    pattern:
      console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

# 腾讯云验证码配置
tencent:
  captcha:
    # 腾讯云API密钥ID
    secretId: xxxxxxxxxxxx
    # 腾讯云API密钥
    secretKey: xxxxxxxxxx
    # 验证码应用ID
    captchaAppId: 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

java 复制代码
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实例
     */
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return objectMapper;
    }
} 
java 复制代码
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 Setters
    public 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包下

java 复制代码
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;
} 
java 复制代码
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

java 复制代码
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;


    /**
     * 初始化腾讯云客户端
     */
    @PostConstruct
    public 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

java 复制代码
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 可以删掉

html 复制代码
<!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文档-腾讯云