首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Spring MVC 實現短信驗證碼功能完整方案:前後端交互、Redis 存儲、接口防刷

     編輯:Java 相關知識

整體架構流程

前端輸入手機號 → 點擊「獲取驗證碼」 → AJAX 請求後端
                                    ↓
後端驗證手機號格式 → 生成 6 位隨機碼 → 存入 Redis (key: phone, value: code, TTL: 5min)
                                    ↓
調用短信服務商 API 發送 → 返回前端成功 → 前端按鈕倒計時 60s
                                    ↓
用戶輸入驗證碼提交表單 → 後端從 Redis 取碼比對 → 刪除 Redis key → 業務邏輯處理

後端核心實現

1. 短信發送控制器

 {
            try {
                smsService.send(phone, "您的驗證碼是:" + code + "," + TTL_MINUTES + " 分鐘內有效。");
            } catch (Exception e) {
                log.error("短信發送失敗: {}", phone, e);
                // 發送失敗可選:刪除已存驗證碼、記錄失敗日誌、告警
            }
        });

        return R.ok();
    }

    @PostMapping("/verify")
    public R<Boolean> verifyCode(@RequestParam String phone, @RequestParam String code) {
        String codeKey = CODE_PREFIX + phone;
        String storedCode = redisTemplate.opsForValue().get(codeKey);
        
        if (storedCode == null) {
            return R.fail("驗證碼已過期或不存在");
        }
        if (!storedCode.equals(code)) {
            return R.fail("驗證碼錯誤");
        }
        
        // 驗證成功後立即刪除,防止重複使用
        redisTemplate.delete(codeKey);
        return R.ok(true);
    }
}

2. 短信服務抽象與實現 (阿里云/騰訊云/自建)

public interface SmsService {
    void send(String phone, String content);
}

@Service
public class AliyunSmsService implements SmsService {
    @Value("${sms.aliyun.access-key}") private String accessKey;
    @Value("${sms.aliyun.secret-key}") private String secretKey;
    @Value("${sms.aliyun.sign-name}") private String signName;
    @Value("${sms.aliyun.template-code}") private String templateCode;

    @Override
    public void send(String phone, String content) {
        // 使用阿里云 SDK 發送
        DefaultAcsClient client = new DefaultAcsClient(
            DefaultProfile.getProfile("cn-hangzhou", accessKey, secretKey));
        
        SendSmsRequest request = new SendSmsRequest();
        request.setPhoneNumbers(phone);
        request.setSignName(signName);
        request.setTemplateCode(templateCode);
        request.setTemplateParam("{\"code\":\"" + extractCode(content) + "\"}");
        
        client.getAcsResponse(request);
    }
    
    private String extractCode(String content) {
        // 從內容中提取純數字碼
        return content.replaceAll("\\D", "");
    }
}

前端實現 (Vue 3 + Axios)

<template>
  <div class="form-group">
    <input v-model="form.phone" type="tel" placeholder="請輸入手機號" />
    <button 
      :disabled="sending || countdown > 0" 
      @click="sendCode"
      class="btn-code"
    >
      {{ countdown > 0 ? countdown + 's 後重試' : '獲取驗證碼' }}
    </button>
  </div>
  
  <input v-model="form.code" type="text" placeholder="請輸入驗證碼" maxlength="6" />
  <button @click="submit" :disabled="submitting">提交</button>
</template>

<script setup>
import { ref } from 'vue'
import api from '@/utils/api'

const form = ref({ phone: '', code: '' })
const countdown = ref(0)
const sending = ref(false)

const sendCode = async () => {
  if (!/^1[3-9]\d{9}$/.test(form.value.phone)) {
    return ElMessage.error('手機號格式不正確')
  }
  sending.value = true
  try {
    await api.post('/api/sms/send', { phone: form.value.phone })
    ElMessage.success('發送成功')
    startCountdown()
  } catch (e) {
    ElMessage.error(e.response?.data?.msg || '發送失敗')
  } finally {
    sending.value = false
  }
}

const startCountdown = () => {
  countdown.value = 60
  const timer = setInterval(() => {
    countdown.value--
    if (countdown.value <= 0) clearInterval(timer)
  }, 1000)
}

const submit = async () => {
  if (!form.value.code) return ElMessage.error('請輸入驗證碼')
  try {
    await api.post('/api/sms/verify', form.value)
    ElMessage.success('驗證通過')
    // 跳轉或執行業務邏輯
  } catch (e) {
    ElMessage.error(e.response?.data?.msg || '驗證失敗')
  }
}
</script>

關鍵安全防護措施

風險防護方案
短信轟炸/惡意刷單Redis 設置發送間隔鎖 (60s) + 日限制 (如 10 次/天) + IP 限制
驗證碼暴力破解Redis 設置驗證碼 TTL (5min) + 驗證失敗計數 (5 次鎖定 15min)
驗證碼重複使用驗證成功後立即 redisTemplate.delete(key)
短信內容模板審核使用服務商模板變量,避免自定義內容被攔截
接口被爬蟲調用增加圖形驗證碼/滑塊驗證 + 簽名校驗 + 限流

Redis Key 設計規範

// 驗證碼存儲
sms:code:{phone}          → "123456" (TTL 5min)
// 發送間隔鎖
sms:code:interval:{phone} → "1" (TTL 60s)
// 日發送次數計數
sms:code:daily:{phone}    → "3" (TTL 24h)
// 驗證失敗計數
sms:code:fail:{phone}     → "2" (TTL 15min)

總結

短信驗證碼系統的核心在於狀態管理 (Redis) 與異步解耦 (發送不阻塞響應)。關鍵點:

  1. 手機號格式 + 圖形驗證碼雙重前置校驗
  2. Redis 原子操作實現分佈式限流與狀態存儲
  3. 驗證碼生成、存儲、發送、驗證、銷毀全生命周期管理
  4. 短信服務商抽象,支持多通道切換與降級
  5. 前端倒計時體驗優化,防止重複點擊
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved