一、短信通道防盗刷方案
一、使用安全图形验证码,增加识别难度,防止通过自动化工具进行攻击请求;
规则:使用手滑动形式的验证码。
二、限制每个手机号的发送次数;
规则:每个手机号每天最多只能发10条短信;
三、单Ip的请求次数限制,防止攻击者对服务器进行大量无效请求(在图形验证码未破解的情况下,自动化工具行程错误请求),增加服务器负担;
规则:限制单IP每天请求次数不能超过10次。
四、单用户动态短信请求间隔时长限制,防止对单个用户形成手工攻击,防止图形验证码失效后对用户形成大量攻击;
规则:单用户请求短信时间间隔为“60秒”;
五、增加IP黑名单库,在黑名单库的Ip永久不能获取验证码;管理员可以手动添加IP黑名单,可以手动删除黑名单;
黑名单规则:
1)同一号码在同一天内发送超过10条短信;
2)同一IP在1分钟内出现3次以上;
3)同一IP在30分钟内超过5次以上;
4)同一IP在60分钟内出现10次以上 ;
5)同一IP在48*60内出现20以上 ;
以上为方案参考
二、参数配置短信策略
三、代码实现
技术方案:
1.将参数存入redis缓存,每次从缓存中获取参数;
2.短信间隔60秒用redis缓存,有效期设置为60秒
3.请求次数存入redis,每次请求加1,到达上限则限制发送短信
====短信策略Service类
package com.shijie.box.service;
import com.shijie.box.db.util.StringUtil;
import com.shijie.box.util.IpUtil;
import com.shijie.box.vo.ApiResult;
import com.shijie.box.vo.SmsFangweiSendRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;
/**
* 短信发送策略 wy
* 2020/7/29
*/
@Service
public class SmsPolicyService {
private static final Logger logger = LoggerFactory.getLogger(SmsPolicyService.class);
private static final String MOBILE_PROFIX = "SMS_MOBILE_";//短信手机号缓存类型
private static final String IP_PROFIX = "SMS_IP_";//短信手机号缓存类型
private static final String SMSTIME = "SMSTIME_";//短信手机号缓存有效时长
private static final String SMS_IP_BLACK_LIST = "SMS_IP_BLACK_LIST";//【短信策略】IP黑名单,以";"间隔
private static final String SMS_IP_WHITE_LIST = "SMS_IP_WHITE_LIST";//【短信策略】IP白名单,当前IP不做任何策略限制,以“;”间隔
private static final String SMS_TIME_SPACING = "SMS_TIME_SPACING";//【短信策略】单用户(IP)请求短信时间间隔为“60秒”
private static final String SMS_COUNT_PER_IP = "SMS_COUNT_PER_IP";//【短信策略】限制单IP每天请求次数不能超过10次
private static final String SMS_COUNT_PER_MOBILE = "SMS_COUNT_PER_MOBILE";//【短信策略】每个手机号每天最多只能发10条短信
@Autowired
com.shijie.box.db.util.RedisUtil redisUtil;
@Resource
com.shijie.box.db.dao.SxbSysParameterMapper SxbSysParameterMapper;
/**
* 短信发送策略
* @param request
* @return
*/
public ApiResult policy(SmsFangweiSendRequest parameter, HttpServletRequest request) {
ApiResult rest=new ApiResult("0000","短信策略通过");
String ipAddr = IpUtil.getIpAddr(request);
String whiteList = redisUtil.get(SMS_IP_WHITE_LIST);//ip白名单
//如果缓存中未存短信策略,则开始缓存
if(StringUtil.isEmpty(whiteList)){
refreshSMSPolicy();
whiteList = redisUtil.get(SMS_IP_WHITE_LIST);//ip白名单
}
//=======如果是IP白名单,直接通过策略===============//
if(whiteList.indexOf(ipAddr)>=0){
logger.info("[短信策略]请求IP:"+ipAddr+",IP白名单放行");
return rest;
}
//===========如果ip黑名单,则直接返回,不通过策略===============//
String blackList = redisUtil.get(SMS_IP_BLACK_LIST);//ip黑名单
if(blackList.indexOf(ipAddr)>=0){
logger.info("[短信策略]请求IP:"+ipAddr+",IP黑名单,做拦截");
return new ApiResult("0001","未发送,当前ip:"+ipAddr+"已被限制发送短信,请联系管理员");
}
//================【短信策略】限制单IP每天请求次数不能超过10次================//
int SMSIpCount = Integer.parseInt(redisUtil.get(SMS_COUNT_PER_IP));
String ipCountKey = IP_PROFIX+ipAddr;
int ipCount = StringUtil.isEmpty(redisUtil.get(ipCountKey))?0: Integer.parseInt(redisUtil.get(ipCountKey));
if(ipCount>=SMSIpCount){
logger.info("[短信策略]请求IP:"+ipAddr+",当前IP,今日发送短信次数过多,已超过"+SMSIpCount+"次");
return new ApiResult("0002","当前IP今日发送短信次数过多,已超过"+SMSIpCount+"次");
}
//================【短信策略】每个手机号每天最多只能发10条短信================//
int SMSMobileCount = Integer.parseInt(redisUtil.get(SMS_COUNT_PER_MOBILE));
String mobileCountKey = MOBILE_PROFIX+parameter.getMobile();
int mobileCount = StringUtil.isEmpty(redisUtil.get(mobileCountKey))?0: Integer.parseInt(redisUtil.get(mobileCountKey));
if(mobileCount>=SMSMobileCount){
logger.info("[短信策略]请求IP:"+ipAddr+",当前手机号"+parameter.getMobile()+",今日发送短信次数过多,已超过"+SMSMobileCount+"次");
return new ApiResult("0002","当前手机号今日发送短信次数过多,已超过"+SMSMobileCount+"次");
}
//================【短信策略】单用户(IP)请求短信时间间隔为“60秒”============//
String rediskeyIp = SMSTIME+ipAddr;
Long smsTimeSpacing = Long.parseLong(redisUtil.get(SMS_TIME_SPACING));//【短信策略】单用户(IP)请求短信时间间隔为“60秒”
if(!StringUtil.isEmpty(redisUtil.get(rediskeyIp))){
logger.info("[短信策略]请求IP:"+ipAddr+",请勿频繁请求,再次获取请间隔");
return new ApiResult("0002","请勿频繁请求,再次获取请间隔"+smsTimeSpacing+"秒");
}
//================【短信策略】限制单IP每天请求次数不能超过10次===当前为修改缓存中的次数=============//
if(ipCount==0){
redisUtil.setEx(ipCountKey,++ipCount+"",86400, TimeUnit.SECONDS);//1天
}else{
redisUtil.setEx(ipCountKey,++ipCount+"",redisUtil.getExpire(ipCountKey), TimeUnit.SECONDS);
}
//================【短信策略】每个手机号每天最多只能发10条短信====当前为修改缓存中的次数============//
if(mobileCount==0){
redisUtil.setEx(mobileCountKey,++mobileCount+"",86400, TimeUnit.SECONDS);//1天
}else{
redisUtil.setEx(mobileCountKey,++mobileCount+"",redisUtil.getExpire(mobileCountKey), TimeUnit.SECONDS);
}
//================【短信策略】单用户(IP)请求短信时间间隔为“60秒”====当前为修改缓存中的秒数========//
//设置当前ip再次发送短信时间间隔为60秒
redisUtil.setEx(rediskeyIp,ipAddr,smsTimeSpacing, TimeUnit.SECONDS);
return rest;
}
/**
* 刷新短信发送策略缓存
*/
public void refreshSMSPolicy(){
//设置缓存有效期为2天,两天后发送短信时候,会再次加载此缓存
redisUtil.setEx(SMS_IP_BLACK_LIST,SxbSysParameterMapper.selectByPrimaryKey(SMS_IP_BLACK_LIST).getpValue().trim(),2, TimeUnit.DAYS);
redisUtil.setEx(SMS_IP_WHITE_LIST,SxbSysParameterMapper.selectByPrimaryKey(SMS_IP_WHITE_LIST).getpValue().trim(),2, TimeUnit.DAYS);
redisUtil.setEx(SMS_TIME_SPACING,SxbSysParameterMapper.selectByPrimaryKey(SMS_TIME_SPACING).getpValue().trim(),2, TimeUnit.DAYS);
redisUtil.setEx(SMS_COUNT_PER_IP,SxbSysParameterMapper.selectByPrimaryKey(SMS_COUNT_PER_IP).getpValue().trim(),2, TimeUnit.DAYS);
redisUtil.setEx(SMS_COUNT_PER_MOBILE,SxbSysParameterMapper.selectByPrimaryKey(SMS_COUNT_PER_MOBILE).getpValue().trim(),2, TimeUnit.DAYS);
}
}
====短信发送Controller
@Controller
@RequestMapping("/sms/fangwei")
@Api("短信相关接口")
public class SmsFangWeiApi {
@Autowired
private HttpServletRequest request;
@Autowired
private SmsFangweiService service;
@PostMapping("/send")
@ApiOperation("发送短信api")
@ResponseBody
public ApiResult<String> send(@RequestBody SmsFangweiSendRequest SmsFangweiSendRequest) {
ApiResult<String> rest= service.send(SmsFangweiSendRequest,request) ;
return rest;
}
}
=====发送短信service
@Service
public class SmsFangweiService {
private static final Logger logger = LoggerFactory.getLogger(SmsFangweiService.class);
@Autowired
private SmsPolicyService SmsPolicyService;
public String send(SmsFangweiSendRequest request, HttpServletRequest httpServletRequest) {
logger.info("进入短信发送策略{}","短信策略参数配置");
//=========短信策略,0000代码策略通过================//
ApiResult policy = SmsPolicyService.policy(request, httpServletRequest);
//如果通过策略则走发短信流程
if("0000".equals(policy.getError_code())) {
//发送短信方法
sendSMS();
}else{
logger.info("【未通过短信策略】,"+policy.getMessage());
}
if(!"0000".equals(policy.getError_code())){
return "error";
}else {
return "success";
}
}
}
代码中用到的两个IPUtil和RedisUtil下载链接:
链接:https://pan.baidu.com/s/1KE0_wXvbqaK2hQR0Ikc_gg
提取码:4ujn
以上如果获取手机流量的IP不准确,可以参考文章:
java获取手机IP地址不准确解决
https://blog.csdn.net/wangyue23com/article/details/107764209