阿里云短信api接入指南

2021-07-12 01:33:02  晓掌柜  版权声明:本文为站长原创文章,转载请写明出处


一、前言

        随着互联网科技迅速发展,在给人带来各种便利的同时也引发了一些引人思考的安全性问题,比如:短信诈骗。所以身处

    于这个大的互联网中,安全问题是每个人都需要关注和重视。

        回到我们身边,随着目前系统业务种类和复杂度的增加,用户手机验证码这部分功能的开发已经提上日程。今天我们就以

    阿里云短信服务为例,聊一下短信验证码那些事儿。

二、短信验证码须知

    2.1、什么是短信验证码

        每个人都有注册和使用app,验证码是这两个环节最为常用的一个认证工具。它是商家给到用户验证身份的一个凭证,

    通过发送手机短信这种简单直接又安全的方式来达到验证用户真实身份的目的。

    2.2、为什么要用短信验证码

        ① 安全

            随着移动互联网的兴起,手机号成为了移动互联的一个重要标识。直接使用短信验证码的方式进行安全性确认,

            可以在用户账户触发高风险操作时确保是本人操作,同时手机号有和个人实名认证绑定,可以很大程度上降低被恶意

            攻击和账户被盗导致的风险。

        ② 方便稳定

            手机基本上成为每个人随身携带的物品了,加上网络运营商短信高效处理效率。我们几乎可以在绝大多数地方数秒内

            收到短信验证。

        ③ 操作方面价格低

            目前各大服务商都很好地支持到了手机短信服务,接入方便,价格方面也是比较便宜的。

        ④ 获取用户联系方式

            从运营角度来看,数据为王。能获取到用户的联系方式,就能构建出用户的信息网。对后期的产品运营有很大的帮助。

            PS: 切记不要滥用用户数据,做遵纪守法的好市民!

三、短信验证码使用场景

    3.1、用户注册

        在用户注册账号时,输入手机号并确认获取验证码操作后,有sms后台发送验证码到用户手机,完成用户身份验证并进行快速登录。

    3.2、安全检测

        用户在使用软件或系统时,如出现高危操作:大量资金转移、异地登录、频繁请求等。需要验证用户身份,保证是用户本人操作。

    3.3、信息变更

        在一些关键性的信息变更,如:密码修改等操作进行时,我们需要通过发送用户预留的手机号短信验证码,验证本人操作后才能

        对关键信息作出修改。

    3.4、支付验证

        支付操作,是一个很重要的环节,在用户进行资金(包括但不限于rmb)支付、转移等操作时。需要发送用户预留手机号进行

        短信验证。

        PS: 诸如此类还有很多场景,简单来说:凡是涉及到高危操作、敏感操作都需要进行用户身份验证。

四、短信服务接入

    这里以阿里云国内短信服务为例,介绍下整个服务接入过程。这里贴上官方产品文档地址:阿里云短信服务产品文档

    4.1、开通服务

        登录阿里云平台,开通并进入短信服务       

        

    4.2、添加签名        

        

        签名添加完成后如下:        

        

        

    4.3、添加模板

                
        


五、业务流程

    5.1、业务流程如下        

        

    5.2、安全性保证

        ① 手机号正确性验证:如果手机号本身就是错误的就没必要进行下一步操作

        ② 签名校验:加入AK和SK,防止接口被盗刷

        ③ 1min检测:一个手机号在1min内不允许重复请求

        ④ 手机号操作上限:定义手机号单量操作次数上线(具体根据实际业务情况而定)

        ⑤ ip操作上限:定义单个ip单量操作次数上线(具体根据实际业务情况而定)

        ⑥ 阿里云风控:借助阿里云短信服务风控,配置接口单日总量及单个手机号发送量预警

        ⑦ 前端部分的验证这里少量的提到(发送验证码置灰、多次操作后需要验证码等)

   

        

六、核心代码  

    6.1、引入阿里云sdk


        <dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.16</version>
</dependency>

    6.2、sms工具类


/**
* @author XA
* date 2021/7/12 10:40
* description: 手机短信业务工具类
*/
@Component
public class SmsUtil {

@Value("${sms.verify.regionId}")
private String regionId;

@Value("${sms.verify.accessKeyId}")
private String accessKeyId;

@Value("${sms.verify.secret}")
private String secret;

@Value("${sms.verify.signName}")
private String signName;

@Value("${sms.verify.templateCode}")
private String templateCode;

@Value("${sms.sendTimes}")
private int sendTimes;

private SmsUtil (){};

private static final Logger logger = LoggerFactory.getLogger(SmsUtil.class);

@Autowired
private RedisUtil redisUtil;


public R sendMsg(SmsVO smsVO){
logger.debug("手机短信接口发送调用:" + smsVO.toString());
String phone = smsVO.getPhone();

/* 短信发送前置验证 */
R verifyRlt = bhVerify(smsVO);
if(1 != (int)verifyRlt.get("code")){
return verifyRlt;
}

/* 短信Code生成 */
String code = buildCode(smsVO);
if("".equals(code)){
return R.error("验证码生成失败!");
}

R sendSmsRlt = sendSms(phone, code);
if(1 != (int)sendSmsRlt.get("code")){
return sendSmsRlt;
}

/* 短信发送后业务处理 */
smsBuiness(code, phone, smsVO.getIp());
return R.error("短信消息发送成功!");
}

/**
* 功能描述: 请求信息验证
* Param: [smsVO]
* Return: com.bh.commons.model.param.R
*/
private R bhVerify(SmsVO smsVO){
/* 手机号合法性验证 */
String phone = smsVO.getPhone();
if(!CommonUtil.isMobileNO(phone)){
return R.error("手机号不合法!");
}
/* todo 签名校验 */
/* 是否在1min之内已经发送了验证码 */
String oneMinKey = "sms:" + phone + ":oneMin";
Object oneMinObj = redisUtil.get(oneMinKey);
if(oneMinObj != null){
return R.error("验证码已经发送,请勿重复操作!");
}
/* 当前手机号是否已经发送了20次(一天)*/
String phoneKey = "sms:" + phone + ":sendTimes";
Object phoneSendTimesObj = redisUtil.get(phoneKey);
if(null != phoneSendTimesObj){
int phoneSendTimes = Integer.parseInt(phoneSendTimesObj.toString());
if(phoneSendTimes > sendTimes){
return R.error("操作过于频繁,请稍后再试!");
}
}
/* 当前ip是否已经发送了20次(一天)*/
String ipKey = "sms:" + smsVO.getIp() + ":sendTimes";
Object ipSendTimesObj = redisUtil.get(ipKey);
if(null != ipSendTimesObj){
int ipSendTimes = (int) ipSendTimesObj;
if(ipSendTimes > sendTimes){
return R.error("操作过于频繁,请稍后再试!");
}
}
return R.ok();
}

/**
* 功能描述: 验证码生成
* Param: [smsVO]
* Return: java.lang.String
*/
private String buildCode(SmsVO smsVO){
for(int i=0;i<20;i++){
long code = Math.round((Math.random() + 1) * 1000);
String codeKey = "sms:codePool" + code;
Object codeObj = redisUtil.get(codeKey);
if(null == codeObj){
return String.valueOf(code);
}
}
return "";
}

private R sendSms(String phone, String code){
DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, secret);
IAcsClient client = new DefaultAcsClient(profile);
CommonRequest request = new CommonRequest();
request.setSysMethod(MethodType.POST);
request.setSysDomain("dysmsapi.aliyuncs.com");
request.setSysVersion("2017-05-25");
request.setSysAction("SendSms");
request.putQueryParameter("PhoneNumbers", phone);
request.putQueryParameter("SignName", signName);
request.putQueryParameter("TemplateCode", templateCode);
request.putQueryParameter("TemplateParam", "{\"bhMsgCode\":" + code + "}");
try {
/* {"RequestId":"9823C61C-16B4-4F54-895A-63A3A4ADE3E4","Message":"OK","BizId":"767206326058387972^0","Code":"OK"} */
CommonResponse response = client.getCommonResponse(request);
String respData = response.getData();
logger.debug("手机短信发送结果:" + respData);
JSONObject respObj = JSONObject.parseObject(respData);
String respCode = respObj.getString("Code");
if("OK".equals(respCode)){
return R.ok();
}
String errMsg = respObj.getString("Message");
logger.error("手机短信验证码发送失败:" + errMsg);
} catch (Exception e) {
logger.error("手机短信验证码发送异常:" + e.getMessage());
e.printStackTrace();
}
return R.error();
}

/**
* 功能描述: 验证码发送成功后业务处理
* Param: [code, phone, ip]
* Return: void
*/
private void smsBuiness(String code, String phone, String ip){
/* 写入codePool(过期时间1min)*/
String codePoolKey = "sms:codePool:" + code;
redisUtil.set(codePoolKey, code, 60 * 10);

/* 写入一分钟内有效数据 */
String oneMinKey = "sms:" + phone + ":oneMin";
redisUtil.set(oneMinKey, code, 60);

/* 手机号(过期时间1d)*/
String phoneKey = "sms:" + phone + ":sendTimes";
redisUtil.incr(phoneKey,1);

/* ip发送记录(过期时间1d)*/
String ipKey = "sms:" + ip + ":sendTimes";
redisUtil.incr(ipKey,1);
}

}

七、后记    

        上述操作是关于短信验证码的一些初步设想。从理念上和技术及代码上都还有长足的完善之处。这里更多的是做一个

    简单的记录。另,后续持续更新更完善的业务及程序逻辑处理。

    附效果展示:

    

    

    更多精彩,请持续关注:guangmuhua.com


最新评论: