其他方案=>引入QQ郵箱發送驗證碼進行安全校驗
相對短信驗證碼,操作更簡單而且免費
最近想給自己的項目在注冊時加點安全校驗,準備使用免費的郵箱驗證來着,在上一篇引入QQ郵箱進行安全校驗時,看有朋友說阿裡雲會送一些短信服務免費額度,于是去官網一看,果然送了100條額度,是以在此寫一篇使用流程與郵箱驗證作為不同解決方案。
一.需求分析
- 場景:使用者輸入自己的手機号,點選擷取驗證碼,背景會發送驗證碼到對應手機号中。
- 分析:防止刷爆服務,可以限制一分鐘内隻能擷取一次。
- 前端:期限内禁用button按鈕。
- 後端:存入redis設定過期時間,請求先判斷redis中是否有資料。
二.服務介紹
- 目前市面上有很多第三方提供的短信服務,這些第三方短信服務會和各個營運商(移動,聯通,電信)對接,我們隻需要購買服務後按照其提供的開發文檔進行調用就可以發送短信了。常用的短信服務:
- 阿裡雲
- 騰訊雲
- 華為雲
由于白嫖的是阿裡雲的免費額度,此文介紹如何引入阿裡雲短信服務~
三.服務配置
首先要到官方對服務進行相關的配置
- 進入阿裡雲官網并登入,頂部搜尋短信服務
引入短信服務發送手機驗證碼進行安全校驗一.需求分析二.服務介紹三.服務配置四.後端開發五.前端(補充) - 進入短信服務控制台
引入短信服務發送手機驗證碼進行安全校驗一.需求分析二.服務介紹三.服務配置四.後端開發五.前端(補充) - 選擇國内消息菜單,首先添加短信簽名,用于辨別短信發送者的身份
引入短信服務發送手機驗證碼進行安全校驗一.需求分析二.服務介紹三.服務配置四.後端開發五.前端(補充) - 其次申請短信模闆,用于定義發送短信的内容格式。
引入短信服務發送手機驗證碼進行安全校驗一.需求分析二.服務介紹三.服務配置四.後端開發五.前端(補充) - 滑鼠移動到右上角使用者頭像上,在彈出的視窗中點選[AccessKey管理],類似于使用者名密碼,提供于程式中通路阿裡雲鑒權
引入短信服務發送手機驗證碼進行安全校驗一.需求分析二.服務介紹三.服務配置四.後端開發五.前端(補充) - 進入後,可以選擇使用子使用者,權限更小,不小心洩露AccessKey導緻的危害比較小,但操作相對繁瑣
引入短信服務發送手機驗證碼進行安全校驗一.需求分析二.服務介紹三.服務配置四.後端開發五.前端(補充) - 首先需要建立一個使用者,可以其中控制隻允許OpenAPI調用通路
引入短信服務發送手機驗證碼進行安全校驗一.需求分析二.服務介紹三.服務配置四.後端開發五.前端(補充) - 建立好後會生成一對AccessKey,需要妥善保管,防止洩露
引入短信服務發送手機驗證碼進行安全校驗一.需求分析二.服務介紹三.服務配置四.後端開發五.前端(補充) - 點選建立好的使用者,給其授予相應的短信服務權限。
引入短信服務發送手機驗證碼進行安全校驗一.需求分析二.服務介紹三.服務配置四.後端開發五.前端(補充)
至此完成了短信服務的相關配置,接下來一起看看如何在項目中使用吧~
四.後端開發
官方提供非常詳細的使用流程,可以選擇自己檢視幫助文檔學習使用.
- 具體開發步驟:
- 導入maven坐标
- 調用API
(1) 環境搭建
- 在maven中導入如下坐标
<!--短信驗證碼所需jar包-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.16</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>2.1.0</version>
</dependency>
- 由于我們需要使用redis緩存驗證碼是以還要導入redis的jar包
<!-- 使用redis緩存驗證碼時效-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 在yml檔案中配置redis,設定了redis密碼記得加上密碼配置
spring:
redis:
# redis資料庫索引(預設為0),我們使用索引為3的資料庫,避免和其他資料庫沖突
database: 3
# redis伺服器位址(預設為localhost)
host: localhost
# redis端口(預設為6379)
port: 6379
(2) 代碼開發
複制官方提供的測試案例,填充入在服務配置中擷取的相應的參數即可。
可在自己項目中根據自己的需求将官方案例封裝為工具類調用
package com.example.utils;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.profile.DefaultProfile;
/**
* 短信發送工具類
*/
public class SMSUtils {
// 簽名
private final static String SIGN_NAME = "XXXX";
// 模闆
private final static String TEMPLATE_CODE = "XXXX";
/**
* 發送短信
*
* @param phoneNumbers 收信人手機号
* @param param 發送的驗證碼
*/
public static void sendMessage(String phoneNumbers, String param) {
// 配置的accessKeyId和secret
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "xxxx", "xxxxxx");
IAcsClient client = new DefaultAcsClient(profile);
SendSmsRequest request = new SendSmsRequest();
request.setSysRegionId("cn-hangzhou");
// 收信人手機号
request.setPhoneNumbers(phoneNumbers);
// 申請的簽名
request.setSignName(SIGN_NAME);
// 申請的模闆
request.setTemplateCode(TEMPLATE_CODE);
// 替換模闆中的參數,必須為Json格式
request.setTemplateParam("{\"code\":\"" + param + "\"}");
try {
// 擷取發送結果
SendSmsResponse response = client.getAcsResponse(request);
System.out.println(response);
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
// 列印處理結果
System.out.println("ErrCode:" + e.getErrCode());
System.out.println("ErrMsg:" + e.getErrMsg());
System.out.println("RequestId:" + e.getRequestId());
}
}
}
編寫短信服務接口:
package com.example.controller;
@RestController
@CrossOrigin("http://localhost:63342")
public class SendCode {
/**
* @param targetPhone 使用者手機号
* @return
*/
@GetMapping("/getCode")
@ResponseBody
public String phone(@RequestParam("targetPhone") String targetPhone) {
//生成六位數驗證碼
int authNum = new Random().nextInt(899999) + 100000;
String authCode = String.valueOf(authNum);
SMSUtils.sendMessage(targetPhone,authCode);
return "發送成功";
}
}
啟動服務測試接口
GET http://localhost:8080/getCode?targetPhone=158xx889
檢視手機我們可以看到成功接收到了驗證碼
至此我們已經成功實作了調用阿裡雲短信服務發送驗證碼的功能
(3) 緩存改進
如果僅僅是上述那樣,當碰到惡意使用者時,我們的财産将面臨非常危險的處境,是以可以引入緩存來簡單改進代碼
package com.example.controller;
import com.example.utils.SMSUtils;
import com.example.utils.SendMailUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@RestController
@CrossOrigin("http://localhost:63342")
public class SendCode {
@Resource
private RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
/**
* @param targetPhone 使用者手機号
* @return
*/
@GetMapping("/getCode")
@ResponseBody
public String phone(@RequestParam("targetPhone") String targetPhone) {
// 發送前先看下我們是否已經緩存了驗證碼
String yzm = redisTemplate.opsForValue().get("yzm");
// 判斷是否存在
if (yzm == null){
// 生成六位數驗證碼
int authNum = new Random().nextInt(899999) + 100000;
String authCode = String.valueOf(authNum);
// 不存在,我們發送驗證碼給使用者
SMSUtils.sendMessage(targetPhone,authCode);
// 存入redis中,設定有效期為1分鐘
redisTemplate.opsForValue().set("yzm", authCode, 1, TimeUnit.MINUTES);
return "發送成功";
}
// 存在,直接傳回,不再發送驗證碼~
return "請勿重複發送驗證碼";
}
}
重新多次測試接口檢視效果,可發現短時間内隻能擷取一次:
如此我們便簡單的完善了擷取驗證碼功能。
五.前端(補充)
用原生js簡單寫了一個界面,感興趣的可以看一看
代碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<input id="phoneNum" type="text">
<button id="getCode">擷取驗證碼</button>
</div>
<script>
/*按鈕禁用60秒,并顯示倒計時*/
function disabledButton() {
const getCode = document.querySelector("#getCode")
getCode.disabled = true
let second = 60;
const intervalObj = setInterval(function () {
getCode.innerText = "請" + second + "秒後再重試"
if (second === 0) {
getCode.innerText = "擷取驗證碼"
getCode.disabled = false
clearInterval(intervalObj);
}
second--;
}, 1000);
}
document.querySelector("#getCode").addEventListener('click', function () {
const phoneNum = document.querySelector("#phoneNum")
let xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:8080/getCode?targetPhone=" + phoneNum.value, true);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
alert(xhr.response);
disabledButton()
}
}
})
</script>
</body>
</html>