✅P214_商城业务-认证服务-验证码防刷校验

gong_yz大约 3 分钟谷粒商城

三方服务短信发送接口

编写短信验证接口,方便其它服务调用。

cfmall-third-party/src/main/java/com/gyz/cfmall/thirdparty/controller/SmsController.java

package com.gyz.cfmall.thirdparty.controller;

import com.gyz.cfmall.thirdparty.component.SmsComponent;
import com.gyz.common.utils.R;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.annotation.Resource;

@Controller
@RequestMapping(value = "/sms")
public class SmsController {
    @Resource
    private SmsComponent smsComponent;

    /**
     * 提供给别的服务进行调用
     *
     * @param phone
     * @param code
     * @return
     */
    @GetMapping(value = "/sendCode")
    public R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code) {
        //发送验证码
        smsComponent.sendCode(phone, code);

        return R.ok();
    }
}

认证服务远程调用发送短信接口

开启远程服务调用功能注解:@EnableFeignClients

认证服务中编写获取短信验证码的controller

@RestController
public class LoginController {

    @Resource
    private ThirdPartFeignService thirdPartFeignService;

    @GetMapping(value = "/sms/sendCode")
    public R sendCode(@RequestParam("phone") String phone) {
        String code = UUID.randomUUID().toString().substring(0, 5);
        thirdPartFeignService.sendCode(phone, code);

        return R.ok();
    }
}

远程调用接口编写:

cfmall-auth-server/src/main/java/com/gyz/cfmall/feign/ThirdPartFeignService.java

package com.gyz.cfmall.feign;

import com.gyz.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient("cfmall-third-party")
public interface ThirdPartFeignService {
    @GetMapping(value = "/sms/sendCode")
    R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code);
}

实现注册页面发送验证码

验证码请求页面修改

cfmall-auth-server/src/main/resources/templates/reg.html

为手机号码input框设置id,方便获取

for="userName" 删除,增加 id="phoneNum"

设置密码输入框设置name属性取值

获取输入框验证码

发送请求,请求后台发送短信验证码

防刷校验思路

问题:有人拿到请求路径恶意请求获取验证码,如何防止一个手机号码限制时间内多次获取短信验证码?

解决思路:

  • 将短信验证码存储在redis中,key为phoneNum,value为:验证码+存储时系统的当前时间;
  • 从redis中查询为null,则调用接口发送短信验证码,若查询不为空则判断是否超过60s,是则再次调用发送短信验证码,否则返回提示信息。
image-20240217130147803
image-20240217130147803

实现步骤

1、导入redis依赖,并配置好redis地址,端口号

cfmall-auth-server/pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、编写验证码前置信息

cfmall-common/src/main/java/com/gyz/common/constant/AuthServerConstant.java

public class AuthServerConstant {

    public static final String SMS_CODE_CACHE_PREFIX = "sms:code:";

}

3、异常枚举

cfmall-common/src/main/java/com/gyz/common/exception/BizCodeEnum.java

public enum BizCodeEnum {
    
    SMS_CODE_EXCEPTION(10002,"验证码获取频率太高,请稍后再试"),
    //省略代码...
    ;
}

4、接口编写

cfmall-auth-server/src/main/java/com/gyz/cfmall/controller/LoginController.java

package com.gyz.cfmall.controller;

import com.gyz.cfmall.feign.ThirdPartFeignService;
import com.gyz.common.constant.AuthServerConstant;
import com.gyz.common.exception.BizCodeEnum;
import com.gyz.common.utils.R;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author gong_yz
 * @Description
 */
@Controller
public class LoginController {

    private static final Logger log = LoggerFactory.getLogger(LoginController.class);

    @Resource
    private ThirdPartFeignService thirdPartFeignService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @GetMapping(value = "/sms/sendCode")
    public R sendCode(@RequestParam("phone") String phone) {
        // 处理一个手机号码60s内多次获取短信验证码问题
        String s = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone);
        if (StringUtils.isNotEmpty(s)) {
            //活动存入redis的时间,用当前时间减去存入redis的时间,判断用户手机号是否在60s内发送验证码
            Long currentTime = Long.parseLong(s.split("_")[1]);
            // 单位为毫秒,因此,60秒要转化为毫秒即60000
            if (currentTime - System.currentTimeMillis() < 60000) {
                return R.error(BizCodeEnum.SMS_CODE_EXCEPTION.getCode(), BizCodeEnum.SMS_CODE_EXCEPTION.getMessage());
            }
        }
        String code = UUID.randomUUID().toString().substring(0, 5);
        stringRedisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone, code + "_" + System.currentTimeMillis(), 10, TimeUnit.MINUTES);
        thirdPartFeignService.sendCode(phone, code);

        return R.ok();
    }
}

5、注册页面的请求发送验证码的回调函数编写

cfmall-auth-server/src/main/resources/templates/reg.html

// 验证码倒计时60s
$(function () {
	$("#sendCode").click(function () {
		//2、倒计时
		if ($(this).hasClass("disabled")) {
			//正在倒计时中
		} else {
			//1、给指定手机号发送验证码
			$.get("/sms/sendCode?phone=" + $("#phoneNum").val(),function (data) {
				if (data.code!=0){
					alert(data.msg);
				}
			});
			timeoutChangeStyle();
		}
	});
});