Commit db628406 authored by 技术部-任文超's avatar 技术部-任文超

/user/login接口添加针对ipv4的密码错误计数器

parent 2d785812
...@@ -37,6 +37,7 @@ public interface Constants { ...@@ -37,6 +37,7 @@ public interface Constants {
String REDIS_VERIFICATION_COUNT = "verification_code_count:"; String REDIS_VERIFICATION_COUNT = "verification_code_count:";
final Long Image_Need_Count=3L; final Long Image_Need_Count=3L;
String REDIS_PASSWORD_ERROR_COUNT = "password_error_count:"; String REDIS_PASSWORD_ERROR_COUNT = "password_error_count:";
String REDIS_PASSWORD_ERROR_COUNT_FOR_IPV4 = "password_error_count_4_ipv4:";
/** /**
* redis中token的key值前缀 * redis中token的key值前缀
*/ */
......
package cn.quantgroup.xyqb.aspect.captcha;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.util.ValidationUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.Charset;
import java.util.*;
/**
* 密码错误限次的校验
* @author 任文超
* @version 1.0.0
* @since 2017-11-23
*/
@Aspect
@Component
public class PasswordErrorFiniteValidateAdvisor {
private static final Logger LOGGER = LoggerFactory.getLogger(PasswordErrorFiniteValidateAdvisor.class);
private static final String SUPER_CAPTCHA_ID = UUID.nameUUIDFromBytes("__QG_APPCLIENT_AGENT__".getBytes(Charset.forName("UTF-8"))).toString();
private static final String SUPER_CAPTCHA = "__SUPERQG__";
/**
* 图形验证码错误计数器生命周期,与图形验证码生命周期保持一致
* 参照点:cn.quantgroup.xyqb.config.captcha.RedisCaptchaStore{
* DEFAULT_EXPIRED_IN = 120L;
* DEFAULT_EXPIRED_TIMEUNIT = TimeUnit.SECONDS;
* }
*/
private static final Long SECONDS = 2 * 60L;
@Autowired
@Qualifier("stringRedisTemplate")
private RedisTemplate<String, String> redisTemplate;
/**
* 密码错误限次切面
*/
@Pointcut("@annotation(cn.quantgroup.xyqb.aspect.captcha.PasswordErrorFiniteValidator)")
private void passwordErrorFiniteValidate() {
}
/**
* 在受保护的接口方法执行前, 执行密码错误限次校验
*
* @param pjp
* @return
* @throws Throwable
*/
@Around("passwordErrorFiniteValidate()")
private Object doFiniteValidate(ProceedingJoinPoint pjp) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 客户端IP
String clientIp = getIp(request);
Long countErrorByIp = getIpv4Count(clientIp);
// 5分钟内不超过3次
if(countErrorByIp > Constants.Image_Need_Count){
return JsonResult.buildErrorStateResult("登录失败", null);
}
return pjp.proceed();
}
// 获取该ipv4密码错误计数器
private Long getIpv4Count(String ipv4) {
if (ValidationUtil.validateIpv4(ipv4) && ValidationUtil.validateLocalIpv4(ipv4)) {
return 0L;
}
String key = getIpKey(ipv4);
String countString = redisTemplate.opsForValue().get(key);
if (StringUtils.isBlank(countString)) {
return 0L;
}
return Long.valueOf(countString);
}
private final static String getIpKey(String ipv4){
return Constants.REDIS_PASSWORD_ERROR_COUNT_FOR_IPV4 + ipv4;
}
/**
* 客户端IP解析
*
* @param request 当前请求,其首部行必须包含形如【SingleToken 13461067662:0123456789abcdef】的UTF-8编码的Base64加密参数
* @return 客户端IP 或 null
*/
private String getIp(HttpServletRequest request) {
Objects.requireNonNull(request, "无效请求");
String ip = request.getHeader("x-real-ip");
if (StringUtils.isBlank(ip)) {
ip = request.getRemoteAddr();
}
//过滤反向代理的ip
String[] stemps = ip.split(",");
if (stemps.length >= 1) {
//得到第一个IP,即客户端真实IP
ip = stemps[0];
}
ip = ip.trim();
if (ip.length() > 23) {
ip = ip.substring(0, 23);
}
return ip;
}
}
package cn.quantgroup.xyqb.aspect.captcha;
import java.lang.annotation.*;
/**
* 密码错误限次的校验标记
* @author 任文超
* @version 1.0.0
* @since 2017-11-23
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PasswordFineteValidator {
}
package cn.quantgroup.xyqb.controller.internal.user; package cn.quantgroup.xyqb.controller.internal.user;
import cn.quantgroup.xyqb.Constants; import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.aspect.captcha.PasswordFineteValidator;
import cn.quantgroup.xyqb.aspect.logcaller.LogHttpCaller; import cn.quantgroup.xyqb.aspect.logcaller.LogHttpCaller;
import cn.quantgroup.xyqb.controller.IBaseController; import cn.quantgroup.xyqb.controller.IBaseController;
import cn.quantgroup.xyqb.entity.Merchant; import cn.quantgroup.xyqb.entity.Merchant;
...@@ -76,6 +77,7 @@ public class UserController implements IBaseController { ...@@ -76,6 +77,7 @@ public class UserController implements IBaseController {
'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
@PasswordFineteValidator
@RequestMapping("/login") @RequestMapping("/login")
public JsonResult login( public JsonResult login(
@RequestParam(required = false, defaultValue = "1") Long channelId, String appChannel, @RequestParam(required = false, defaultValue = "1") Long channelId, String appChannel,
...@@ -523,11 +525,24 @@ public class UserController implements IBaseController { ...@@ -523,11 +525,24 @@ public class UserController implements IBaseController {
} }
//验证密码 //验证密码
if (!validatePassword(pass, user.getPassword())) { if (!validatePassword(pass, user.getPassword())) {
String ipv4 = getIp();
if (StringUtils.isNotBlank(ipv4) && !ValidationUtil.validateLocalIpv4(ipv4)) {
String ipv4Key = getIpKey(getIp());
if(!stringRedisTemplate.hasKey(getIpKey(getIp()))){
// 计数周期5分钟
stringRedisTemplate.opsForValue().set(ipv4Key, String.valueOf(0), 5, TimeUnit.MINUTES);
}
stringRedisTemplate.opsForValue().increment(ipv4Key, 1L);
}
return null; return null;
} }
return user; return user;
} }
private final static String getIpKey(String ipv4){
return Constants.REDIS_PASSWORD_ERROR_COUNT_FOR_IPV4 + ipv4;
}
private boolean validatePassword(String paramPass, String targetPassword) { private boolean validatePassword(String paramPass, String targetPassword) {
return StringUtils.defaultString(targetPassword, "").equals(PasswordUtil.MD5(paramPass.toLowerCase() + pwdSalt)); return StringUtils.defaultString(targetPassword, "").equals(PasswordUtil.MD5(paramPass.toLowerCase() + pwdSalt));
} }
......
...@@ -11,33 +11,49 @@ import java.util.regex.Pattern; ...@@ -11,33 +11,49 @@ import java.util.regex.Pattern;
*/ */
public class ValidationUtil { public class ValidationUtil {
private static String regExp = "^((13[0-9])|(14[0-9])|(15[0-9])|(17[0-9])|(18[0-9])|(19[0-9]))\\d{8}$"; private static String phoneRegExp = "^((13[0-9])|(14[0-9])|(15[0-9])|(17[0-9])|(18[0-9])|(19[0-9]))\\d{8}$";
private static String chineseExp = "^[\u4e00-\u9fa5]+(\\.|·)?[\u4e00-\u9fa5]+$"; private static String chineseNameRegExp = "^[\u4e00-\u9fa5]+(\\.|·)?[\u4e00-\u9fa5]+$";
private static String ipv4RegExp = "^((2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?)\\.){3}(2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?)$";
private static String localIpv4RegExp = "^172(\\.2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?){3}$";
private static Pattern phonePattern = Pattern.compile(regExp); private static Pattern phonePattern = Pattern.compile(phoneRegExp);
private static Pattern chinesePattern = Pattern.compile(chineseExp); private static Pattern chinesePattern = Pattern.compile(chineseNameRegExp);
private static Pattern ipv4Pattern = Pattern.compile(ipv4RegExp);
private static Pattern localIpv4Pattern = Pattern.compile(localIpv4RegExp);
public static boolean validatePhoneNo(String phoneNo) { public static boolean validatePhoneNo(String phoneNo) {
boolean lengthValid = StringUtils.isNotEmpty(phoneNo) && phoneNo.length() == 11 && StringUtils.isNumeric(phoneNo); boolean lengthValid = StringUtils.isNotEmpty(phoneNo) && phoneNo.length() == 11 && StringUtils.isNumeric(phoneNo);
if (!lengthValid) { if (!lengthValid) {
return false; return false;
} }
Matcher matcher = phonePattern.matcher(phoneNo); Matcher matcher = phonePattern.matcher(phoneNo);
return matcher.find(); return matcher.find();
} }
public static boolean validateChinese(String chinese) { public static boolean validateChinese(String chinese) {
if (StringUtils.isEmpty(chinese)) { if (StringUtils.isBlank(chinese)) {
return false; return false;
} }
Matcher matcher = chinesePattern.matcher(chinese); Matcher matcher = chinesePattern.matcher(chinese);
return matcher.find(); return matcher.find();
} }
public static boolean validateIpv4(String ipv4) {
if (StringUtils.isBlank(ipv4)) {
return false;
}
Matcher matcher = ipv4Pattern.matcher(ipv4);
return matcher.find();
}
public static boolean validateLocalIpv4(String localIpv4) {
if (StringUtils.isBlank(localIpv4)) {
return false;
}
Matcher matcher = localIpv4Pattern.matcher(localIpv4);
return matcher.find();
}
public static boolean validateChannelId(Long channelId) { public static boolean validateChannelId(Long channelId) {
return channelId == 0L ? false : true; return channelId == 0L ? false : true;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment