Skip to content
三方集成2026年6月25日
# 阿里云 # 短信服务

阿里云短信验证码实现(个人可用方案)

一、背景说明

目前阿里云传统短信服务对资质要求较高,通常需要企业或政府机构主体,个人开发者很难直接开通。

但阿里云并不是只有这一条路。个人开发者可以使用号码认证服务来完成短信验证码发送与校验能力。

在控制台搜索:号码认证服务

进入控制台后,这里以“融合认证(基于原子能力)”为例演示完整流程。

如果账户余量不足,先小额充值即可完成测试。

官方测试页参考:

调用测试时按官方文档提示操作即可,本文重点放在代码接入与业务流程上。

号码认证短信能力主要分为两类:

  • 发送:发送验证码。验证码可以由开发者生成,也可以由官方生成。
  • 核验:校验验证码是否正确。官方核验仅支持“官方生成”的验证码;如果验证码是开发者自定义生成,则需在业务侧自行校验。

二、本文方案

本文演示的是:自定义验证码生成 + Redis 存储 + 业务侧校验

环境:

  • Spring Boot 3.5.x

三、依赖准备

xml
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.44</version>
</dependency>
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>dysmsapi20180501</artifactId>
    <version>1.0.12</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>alibabacloud-dypnsapi20170525</artifactId>
    <version>2.0.0</version>
</dependency>
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-core</artifactId>
    <version>4.6.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

四、核心代码实现

1)验证码生成工具

java
public class RandomUtil {
    /*
     * 随机生成6位数字验证码
     */
    public static String getRandomCode() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 6; i++) {
            sb.append((int) (Math.random() * 10));
        }
        return sb.toString();
    }
}

2)Redis 工具类

java
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Component
public class RedisUtil {

    private final StringRedisTemplate stringRedisTemplate;

    public RedisUtil(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /* ===================== String ===================== */

    /**
     * 设置值
     */
    public void set(String key, String value) {
        stringRedisTemplate.opsForValue().set(key, value);
    }

    /**
     * 设置值并指定过期时间
     */
    public void set(String key, String value, long timeout, TimeUnit unit) {
        stringRedisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    /**
     * 获取值
     */
    public String get(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 删除key
     */
    public Boolean delete(String key) {
        return stringRedisTemplate.delete(key);
    }

    /**
     * 批量删除
     */
    public Long delete(Collection<String> keys) {
        return stringRedisTemplate.delete(keys);
    }

    /**
     * 判断key是否存在
     */
    public Boolean hasKey(String key) {
        return stringRedisTemplate.hasKey(key);
    }

    /**
     * 设置过期时间
     */
    public Boolean expire(String key, long timeout, TimeUnit unit) {
        return stringRedisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获取过期时间
     */
    public Long getExpire(String key) {
        return stringRedisTemplate.getExpire(key);
    }

    /**
     * 自增
     */
    public Long increment(String key, long delta) {
        return stringRedisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 自减
     */
    public Long decrement(String key, long delta) {
        return stringRedisTemplate.opsForValue().increment(key, -delta);
    }

    /* ===================== Hash ===================== */

    /**
     * hash put
     */
    public void hSet(String key, String hashKey, String value) {
        stringRedisTemplate.opsForHash().put(key, hashKey, value);
    }

    /**
     * hash get
     */
    public Object hGet(String key, String hashKey) {
        return stringRedisTemplate.opsForHash().get(key, hashKey);
    }

    /**
     * 获取整个 hash
     */
    public Map<Object, Object> hGetAll(String key) {
        return stringRedisTemplate.opsForHash().entries(key);
    }

    /**
     * 删除 hash 中的字段
     */
    public Long hDelete(String key, Object... hashKeys) {
        return stringRedisTemplate.opsForHash().delete(key, hashKeys);
    }

}

3)阿里云短信工具类

java
import com.alibaba.fastjson2.JSONObject;
import com.aliyun.auth.credentials.Credential;
import com.aliyun.auth.credentials.provider.DefaultCredentialProvider;
import com.aliyun.auth.credentials.provider.StaticCredentialProvider;
import com.aliyun.sdk.service.dypnsapi20170525.AsyncClient;
import com.aliyun.sdk.service.dypnsapi20170525.models.CheckSmsVerifyCodeRequest;
import com.aliyun.sdk.service.dypnsapi20170525.models.CheckSmsVerifyCodeResponse;
import com.aliyun.sdk.service.dypnsapi20170525.models.SendSmsVerifyCodeRequest;
import com.aliyun.sdk.service.dypnsapi20170525.models.SendSmsVerifyCodeResponse;
import com.google.gson.Gson;
import darabonba.core.client.ClientOverrideConfiguration;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.concurrent.CompletableFuture;

@Component
public class AliSMSUtil {
    @Value("${aliyun.sms.secret}")
    private String ACCESS_KEY_SECRET;
    @Value("${aliyun.sms.id}")
    private String ACCESS_KEY_ID;

    StaticCredentialProvider provider;

    @Resource
    private RedisUtil redisUtil;

    private static final String REDIS_KEY = "sms:";

    @PostConstruct
    public void init() {
        Credential builder = Credential.builder().accessKeyId(ACCESS_KEY_ID).accessKeySecret(ACCESS_KEY_SECRET).build();
        provider = StaticCredentialProvider.create(builder);
    }

    public boolean sendSMSByAuth(String phone, String code) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", code);
        jsonObject.put("min", "5");

        try (AsyncClient client = AsyncClient.builder()
                .region("cn-chengdu")
                .credentialsProvider(provider)
                .overrideConfiguration(
                        ClientOverrideConfiguration.create()
                                .setEndpointOverride("dypnsapi.aliyuncs.com")
                )
                .build()) {
            SendSmsVerifyCodeRequest sendSmsVerifyCodeRequest = SendSmsVerifyCodeRequest.builder()
                    .signName("速通互联验证平台")
                    .templateCode("100001")
                    .phoneNumber(phone)
                    .templateParam(jsonObject.toJSONString())
                    .build();

            CompletableFuture<SendSmsVerifyCodeResponse> response = client.sendSmsVerifyCode(sendSmsVerifyCodeRequest);
            SendSmsVerifyCodeResponse resp = response.get();
            return resp.getBody().getSuccess();
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean verifyFromRedis(String phone, String code) {
        if (!redisUtil.hasKey(REDIS_KEY + phone)) {
            return false;
        }
        return redisUtil.get(REDIS_KEY + phone).equals(code);
    }
}

五、配置项

yml
aliyun:
  sms:
    id: 你的ACCESS_KEY_ID
    secret: 你的ACCESS_KEY_SECRET

ACCESS_KEY_IDACCESS_KEY_SECRET 可以在阿里云搜索“RAM 访问控制”,进入用户列表后获取。

六、发送与校验示例

发送验证码方法:

java
public ApiResponse<String> sendSMS(String phone) {
    // 先检查是否已经发送了验证码
    if (redisUtil.hasKey(REDIS_KEY + phone)) {
        return ApiResponse.failure(HttpStatusCodeEnum.TOO_MANY_REQUESTS.getCode(), "验证码已发送,请勿重复发送");
    }
    String code = RandomUtil.getRandomCode();
    if (aliSMSUtil.sendSMSByAuth(phone, code)) {
        redisUtil.set(REDIS_KEY + phone, code, 5, TimeUnit.MINUTES);
        return ApiResponse.success("验证码发送成功", null);
    }
    return ApiResponse.failure(HttpStatusCodeEnum.INTERNAL_SERVER_ERROR.getCode(), "验证码发送失败");
}

验证码校验直接调用:

  • AliSMSUtil#verifyFromRedis(phone, code)

七、整体流程

网站运行 ,站长活了: