赞
踩
项目里面有用到用户手机号注册发短信功能,需要做短信防刷机制。主要验证逻辑如下
通过这一套逻辑,可以防刷99%的用户了
<!--验证码-->
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
public enum LoginCodeEnum { /** * 算数 */ ARITHMETIC, /** * 中文 */ CHINESE, /** * 中文闪图 */ CHINESE_GIF, /** * 闪图 */ GIF, SPEC }
@Data public class LoginCode { /** * 验证码配置 */ private LoginCodeEnum codeType; /** * 验证码有效期 分钟 */ private Long expiration = 120L; /** * 验证码内容长度 */ private int length = 2; /** * 验证码宽度 */ private int width = 111; /** * 验证码高度 */ private int height = 36; /** * 验证码字体 */ private String fontName; /** * 字体大小 */ private int fontSize = 25; /** * 验证码前缀 * * @return */ private String codeKey; }
@Configuration
public class ConfigBeanConfiguration {
@Bean
@ConfigurationProperties(prefix = "login")
public LoginProperties loginProperties() {
return new LoginProperties();
}
}
@Data public class LoginProperties { private LoginCode loginCode; /** * 获取验证码生产类 * @return */ public Captcha getCaptcha(){ if(Objects.isNull(loginCode)){ loginCode = new LoginCode(); if(Objects.isNull(loginCode.getCodeType())){ loginCode.setCodeType(LoginCodeEnum.ARITHMETIC); } } return switchCaptcha(loginCode); } /** * 依据配置信息生产验证码 * @param loginCode * @return */ private Captcha switchCaptcha(LoginCode loginCode){ Captcha captcha = null; synchronized (this){ switch (loginCode.getCodeType()){ case ARITHMETIC: captcha = new FixedArithmeticCaptcha(loginCode.getWidth(),loginCode.getHeight()); captcha.setLen(loginCode.getLength()); break; case CHINESE: captcha = new ChineseCaptcha(loginCode.getWidth(),loginCode.getHeight()); captcha.setLen(loginCode.getLength()); break; case CHINESE_GIF: captcha = new ChineseGifCaptcha(loginCode.getWidth(),loginCode.getHeight()); captcha.setLen(loginCode.getLength()); break; case GIF: captcha = new GifCaptcha(loginCode.getWidth(),loginCode.getHeight()); captcha.setLen(loginCode.getLength()); break; case SPEC: captcha = new SpecCaptcha(loginCode.getWidth(),loginCode.getHeight()); captcha.setLen(loginCode.getLength()); default: System.out.println("验证码配置信息错误!正确配置查看 LoginCodeEnum "); } } if(StringUtils.isNotBlank(loginCode.getFontName())){ captcha.setFont(new Font(loginCode.getFontName(), Font.PLAIN,loginCode.getFontSize())); } return captcha; } static class FixedArithmeticCaptcha extends ArithmeticCaptcha { public FixedArithmeticCaptcha(int width,int height){ super(width,height); } @Override protected char[] alphas() { // 生成随机数字和运算符 int n1 = num(1, 10), n2 = num(1, 10); int opt = num(3); // 计算结果 int res = new int[]{n1 + n2, n1 - n2, n1 * n2}[opt]; // 转换为字符运算符 char optChar = "+-x".charAt(opt); this.setArithmeticString(String.format("%s%c%s=?", n1, optChar, n2)); this.chars = String.valueOf(res); return chars.toCharArray(); } } }
@Data
public class CaptchaCodeDto {
private String img;
private String id;
}
@RestController @RequestMapping("/v1/captcha") @Api(tags = "业务-验证码接口") @RequiredArgsConstructor @Slf4j public class CaptchaController { private final LoginProperties loginProperties; private final RedisService redisService; @ApiOperation(value = "获取图形验证码", notes = "获取图形验证码") @GetMapping("/code") public ResultInfo<CaptchaCodeDto> getCode() { Captcha captcha = loginProperties.getCaptcha(); String uuid = "code-key-" + IdUtil.simpleUUID(); // 当验证码类型为 arithmetic时且长度 >= 2 时,captcha.text()的结果有几率为浮点型 String captchaValue = captcha.text(); if (captcha.getCharType() - 1 == LoginCodeEnum.ARITHMETIC.ordinal() && captchaValue.contains(".")) { captchaValue = captchaValue.split("\\.")[0]; } // 保存 redisService.set(uuid, captchaValue, loginProperties.getLoginCode().getExpiration()); // 验证码信息 CaptchaCodeDto captchaCodeDto = new CaptchaCodeDto(); captchaCodeDto.setImg(captcha.toBase64()); captchaCodeDto.setId(uuid); return ResultInfo.success(captchaCodeDto); } }
@Log(title = "common.org", businessType = BusinessType.INSERT, operatorType = OperatorType.BUSINESS) @RequestMapping(value = "/v1/sendMsg", method = RequestMethod.POST) @ApiOperation(value = "发送验证码", notes = "发送验证码") public AjaxResult sendMsg(@RequestBody @Validated SendMsgDto dto) { // 图形验证码验证 Object captchaResult = redisService.get(dto.getId()); redisService.del(dto.getId()); Utils.assertNotEmpty(captchaResult, "图形验证码已过期,请点击图形验证码刷新后重试!"); Utils.assertEquals(captchaResult.toString(), dto.getCaptchaResult(), "图形验证码错误!"); // IP验证 String ip = IpUtils.getIpAddr(ServletUtils.getRequest()); Integer count = Optional.ofNullable((Integer) redisService.get(ip)).orElse(0); Utils.assertTrue(count < 20, "今日使用验证码次数超限!"); redisService.set(ip, ++count, 86400); // 判断5分钟之内是否发送了验证码,如果有的话就发送重复的至用户手机,减少有时候并发引起的重复发送不同的验证码用户不知道用哪个 String key = RedisKey.SMSCODE + dto.getPhone(); // 查询redis中是否有此号码的验证码,如果有就发送老的 Integer verificationCode = Integer.valueOf(RandomValueUtils.randomCode(4)); Object oldVerificationCode = redisService.get(key); if (Utils.isNotEmpty(oldVerificationCode)) { verificationCode = (Integer) oldVerificationCode; } msg = "#code#=" + verificationCode; redisService.set(key, verificationCode, 300); // 执行真正的发短信逻辑 }
import java.net.InetAddress; import java.net.UnknownHostException; import javax.servlet.http.HttpServletRequest; /** * 获取IP方法 * * @author Runner */ public class IpUtils { public static String getIpAddr(HttpServletRequest request) { if (request == null) { return "unknown"; } String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Forwarded-For"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Real-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip; } public static boolean internalIp(String ip) { byte[] addr = textToNumericFormatV4(ip); return internalIp(addr) || "0:0:0:0:0:0:0:1".equals(ip) || "127.0.0.1".equals(ip); } private static boolean internalIp(byte[] addr) { if(addr == null) return false; final byte b0 = addr[0]; final byte b1 = addr[1]; // 10.x.x.x/8 final byte SECTION_1 = 0x0A; // 172.16.x.x/12 final byte SECTION_2 = (byte) 0xAC; final byte SECTION_3 = (byte) 0x10; final byte SECTION_4 = (byte) 0x1F; // 192.168.x.x/16 final byte SECTION_5 = (byte) 0xC0; final byte SECTION_6 = (byte) 0xA8; switch (b0) { case SECTION_1: return true; case SECTION_2: if (b1 >= SECTION_3 && b1 <= SECTION_4) { return true; } case SECTION_5: switch (b1) { case SECTION_6: return true; } default: return false; } } /** * 将IPv4地址转换成字节 * * @param IPv4地址 * @return byte 字节 */ public static byte[] textToNumericFormatV4(String text) { if (text.length() == 0) { return null; } byte[] bytes = new byte[4]; String[] elements = text.split("\\.", -1); try { long l; int i; switch (elements.length) { case 1: l = Long.parseLong(elements[0]); if ((l < 0L) || (l > 4294967295L)) return null; bytes[0] = (byte) (int) (l >> 24 & 0xFF); bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); bytes[3] = (byte) (int) (l & 0xFF); break; case 2: l = Integer.parseInt(elements[0]); if ((l < 0L) || (l > 255L)) return null; bytes[0] = (byte) (int) (l & 0xFF); l = Integer.parseInt(elements[1]); if ((l < 0L) || (l > 16777215L)) return null; bytes[1] = (byte) (int) (l >> 16 & 0xFF); bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); bytes[3] = (byte) (int) (l & 0xFF); break; case 3: for (i = 0; i < 2; ++i) { l = Integer.parseInt(elements[i]); if ((l < 0L) || (l > 255L)) return null; bytes[i] = (byte) (int) (l & 0xFF); } l = Integer.parseInt(elements[2]); if ((l < 0L) || (l > 65535L)) return null; bytes[2] = (byte) (int) (l >> 8 & 0xFF); bytes[3] = (byte) (int) (l & 0xFF); break; case 4: for (i = 0; i < 4; ++i) { l = Integer.parseInt(elements[i]); if ((l < 0L) || (l > 255L)) return null; bytes[i] = (byte) (int) (l & 0xFF); } break; default: return null; } } catch (NumberFormatException e) { return null; } return bytes; } public static String getHostIp() { try { return InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { } return "127.0.0.1"; } public static String getHostName() { try { return InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { } return "未知"; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。