spring boot: use redis+lua to limit the frequency of sending SMS verification codes (spring boot 2.3.2)

1. Why limit the sending frequency of SMS verification codes?

1, SMS verification code Each SMS has cost constraints,

Certainly can not be brushed the random hair of the interface

And the interface is brushed will affect the user's experience,

Affect the normal access of the server,

So even with the protection of graphic verification codes, etc.,

We still have to limit how often SMS verification codes are sent


2. The values ​​I use in the demo project are:

Repeated sending of the same phone number within 60 seconds is prohibited

The same mobile number can send up to 10 messages a day

The valid time of the verification code is 300 seconds

You can adjust it according to your business needs


3. When using in the production environment, it is necessary to add parameter validation/anti-csrf/form idempotency check to the form, etc.

This article is for reference only


Description: Liu Hongdi's Architecture Forest is a blog focusing on architecture. The address is: https://www.cnblogs.com/architectforest

The corresponding source code can be obtained here: https://github.com/liuhongdi/

Description: Author: Liu Hongdi Email: 371125307@qq.com


2. Information about the demo project

1, Project address:



2, project function description:

Use redis to save verification code data and implement time control

I use luosimao's sdk for sending SMS,

You can modify it according to your actual situation


3. Project structure, as shown in the figure:

Three, configuration file description


        <!--redis begin-->
        <!--redis   end-->

        <!--luosimao send sms begin-->
        <!--luosimao send sms   end-->

Description: Introduced sdk and redis access dependencies for texting






Configured access to redis


Four, lua code description


local key = KEYS[1]
local keyseconds = tonumber(KEYS[2])
local daycount = tonumber(KEYS[3])
local keymobile = 'SmsAuthKey:'..key
local keycount = 'SmsAuthCount:'..key
--redis.log(redis.LOG_NOTICE,' keyseconds: '..keyseconds..';daycount:'..daycount)
local current = redis.call('GET', keymobile)
--redis.log(redis.LOG_NOTICE,' current: keymobile:'..current)
if current == false then
   --redis.log(redis.LOG_NOTICE,keymobile..' is nil ')
   local count = redis.call('GET', keycount)
   if count == false then
      redis.call('SET', keycount,1)

      redis.call('SET', keymobile,1)
      return '1'
      local num_count = tonumber(count)
      if num_count+1 > daycount then
         return '2'

         redis.call('SET', keymobile,1)
         return '1'
   --redis.log(redis.LOG_NOTICE,keymobile..' is not nil ')
   return '0'

Note: The number of SMS verification codes should not exceed the specified number per day, and no notification letter has been sent within 60 seconds.

only returns 1, indicating that it can be sent

Return 2: Indicates that the number of bars has exceeded

Return 0: It means that the last text message has not been sent more than 60 seconds


Five, java code description


public class RedisLuaUtil {
    private StringRedisTemplate stringRedisTemplate;
    //private static final Logger logger = LogManager.getLogger("bussniesslog");
    run a lua script
    luaFileName: lua file name,no path
    keyList: list for redis key
    return 0: fail
           1: success
    public String runLuaScript(String luaFileName, List<String> keyList) {
        DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/"+luaFileName)));
        String result = "";
        String argsone = "none";
        try {
            result = stringRedisTemplate.execute(redisScript, keyList,argsone);
        } catch (Exception e) {
            //logger.error("An exception occurs",e);
        return result;

Used to call lua programs



public class AuthCodeUtil {

    //verification code length
    private static final int AUTHCODE_LENGTH = 6;
    //The valid time of the verification code is 300 seconds
    private static final int AUTHCODE_TTL_SECONDS = 300;
    private static final String AUTHCODE_PREFIX = "AuthCode:";

    private RedisTemplate redisTemplate;

    //get a auth code
    public String getAuthCodeCache(String mobile){
        String authcode = (String) redisTemplate.opsForValue().get(AUTHCODE_PREFIX+mobile);
        return authcode;

    //Save the verification code to the cache
    public void setAuthCodeCache(String mobile,String authcode){
        redisTemplate.opsForValue().set(AUTHCODE_PREFIX+mobile,authcode,AUTHCODE_TTL_SECONDS, TimeUnit.SECONDS);

    //make a auth code
    public static String newAuthCode(){
        String code = "";
        Random random = new Random();
        for (int i = 0; i < AUTHCODE_LENGTH; i++) {
            //already setup bound After the parameter, the value range is[0, bound),If no parameter is written, the value is int scope,-2^31 ~ 2^31-1
            code += random.nextInt(10);
        return code;

Generate verification code, save verification code to redis, get verification code from redis



public class SmsUtil {
    private RedisLuaUtil redisLuaUtil;
   //Rules for sending verification codes: same mobile phone number:
    //60 Duplicate sending is not allowed within seconds
     private static final String SEND_SECONDS = "60";
    //Up to 10 posts in one day
     private static final String DAY_COUNT = "10";
    private static final String SMS_APP_SECRET = "key-thisisademonotarealappsecret";

     //Send verification code SMS
    public String sendAuthCodeSms(String mobile,String authcode){

        Client client = Client.create();
        client.addFilter(new HTTPBasicAuthFilter(
        WebResource webResource = client.resource(
        MultivaluedMapImpl formData = new MultivaluedMapImpl();
        formData.add("mobile", mobile);
        formData.add("message", "Verification code:"+authcode+"[Mall]");
        ClientResponse response =  webResource.type( MediaType.APPLICATION_FORM_URLENCODED ).
                post(ClientResponse.class, formData);
        String textEntity = response.getEntity(String.class);
        int status = response.getStatus();
        return "SMS sent";

    //Determine whether a mobile phone number can send a verification code SMS
    public String isAuthCodeCanSend(String mobile) {
        List<String> keyList = new ArrayList();
        String res = redisLuaUtil.runLuaScript("smslimit.lua",keyList);
        System.out.println("------------------lua res:"+res);
        return res;

Determine whether SMS can be sent, send SMS



public class RedisConfig {
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        //use Jackson2JsonRedisSerializer to serialize and deserialize redis of value value
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        //use StringRedisSerializer to serialize and deserialize redis of ke
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        //open transaction
        return redisTemplate;

Configure access to redis



public class HomeController {
    private SmsUtil smsUtil;
    private AuthCodeUtil authCodeUtil;

    //Send a verification code SMS
    public String send(@RequestParam(value="mobile",required = true,defaultValue = "") String mobile) {

         String returnStr = "";
         String res = smsUtil.isAuthCodeCanSend(mobile);
         if (res.equals("1")) {
             //generate a verification code
             String authcode=authCodeUtil.newAuthCode();
             //Save the verification code to the cache
             //send messages
             return smsUtil.sendAuthCodeSms(mobile,authcode);
         } else if (res.equals("0")) {
             returnStr = "Please wait more than 60 seconds before texting";
         } else if (res.equals("2")) {
             returnStr = "The current mobile phone number has exceeded the limit of sending in this day";
         return returnStr;

    //Check if the verification code is correct
    public String auth(@RequestParam(value="mobile",required = true,defaultValue = "") String mobile,
                       @RequestParam(value="authcode",required = true,defaultValue = "") String authcode) {
        String returnStr = "";
        String authCodeCache = authCodeUtil.getAuthCodeCache(mobile);
        if (authCodeCache.equals(authcode)) {
            returnStr = "The verification code is correct";
        } else {
            returnStr = "Verification code error";
        return returnStr;

Send verification code and check whether the verification code is valid


Six, the effect test

1, visit: (note to replace your mobile phone number)


SMS sent

Continuous refresh within 60 seconds returns:

Please wait more than 60 seconds before texting

If there are more than 10, return:

The current mobile phone number has exceeded the limit of sending in this day


2, Verify:

If valid it will return:

The verification code is correct


Seven, check the version of spring boot:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 :: Spring Boot ::        (v2.3.2.RELEASE)


Tags: Java Redis Spring Spring Boot lua sms

Posted by shashiku on Wed, 25 May 2022 21:42:39 +0300