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:
https://github.com/liuhongdi/sendsms
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
1,pom.xml
<!--redis begin--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.11.1</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.11.1</version> </dependency> <!--redis end--> <!--luosimao send sms begin--> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>api</artifactId> <version>1.19</version> <scope>system</scope> <systemPath>${project.basedir}/src/main/resources/jar/jersey-bundle-1.19.jar</systemPath> </dependency> <dependency> <groupId>org.json</groupId> <artifactId>json</artifactId> <version>1.0</version> <scope>system</scope> <systemPath>${project.basedir}/src/main/resources/jar/json-org.jar</systemPath> </dependency> <!--luosimao send sms end-->
Description: Introduced sdk and redis access dependencies for texting
2,application.properties
#error server.error.include-stacktrace=always #errorlog logging.level.org.springframework.web=trace #redis spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.password=lhddemo #redis-lettuce spring.redis.lettuce.pool.max-active=8 spring.redis.lettuce.pool.max-wait=1 spring.redis.lettuce.pool.max-idle=8 spring.redis.lettuce.pool.min-idle=0
Configured access to redis
Four, lua code description
1,smslimit.lua
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('EXPIRE',keycount,86400) redis.call('SET', keymobile,1) redis.call('EXPIRE',keymobile,keyseconds) return '1' else local num_count = tonumber(count) if num_count+1 > daycount then return '2' else redis.call('INCRBY',keycount,1) redis.call('SET', keymobile,1) redis.call('EXPIRE',keymobile,keyseconds) return '1' end end else --redis.log(redis.LOG_NOTICE,keymobile..' is not nil ') return '0' end
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
1,RedisLuaUtil.java
@Service public class RedisLuaUtil { @Resource 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))); redisScript.setResultType(String.class); 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
2,AuthCodeUtil.java
@Component 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:"; @Resource 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
3,SmsUtil.java
@Component public class SmsUtil { @Resource 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"; //key 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( "api",SMS_APP_SECRET)); WebResource webResource = client.resource( "http://sms-api.luosimao.com/v1/send.json"); 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(); keyList.add(mobile); keyList.add(SEND_SECONDS); keyList.add(DAY_COUNT); String res = redisLuaUtil.runLuaScript("smslimit.lua",keyList); System.out.println("------------------lua res:"+res); return res; } }
Determine whether SMS can be sent, send SMS
4,RedisConfig.java
@Configuration public class RedisConfig { @Bean 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 redisTemplate.setEnableTransactionSupport(true); redisTemplate.setConnectionFactory(redisConnectionFactory); return redisTemplate; } }
Configure access to redis
5,HomeController.java
@RestController @RequestMapping("/home") public class HomeController { @Resource private SmsUtil smsUtil; @Resource private AuthCodeUtil authCodeUtil; //Send a verification code SMS @GetMapping("/send") 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 authCodeUtil.setAuthCodeCache(mobile,authcode); //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 @GetMapping("/auth") 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); System.out.println(":"+authCodeCache+":"); 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)
http://127.0.0.1:8080/home/send?mobile=13888888888
return:
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:
http://127.0.0.1:8080/home/auth?mobile=13888888888&authcode=638651
If valid it will return:
The verification code is correct
Seven, check the version of spring boot:
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.3.2.RELEASE)