Commit fbb8bec3 authored by Java—红包—徐 然's avatar Java—红包—徐 然

Merge branch 'master' into 20171130-forbidden-wechat

# Conflicts:
#	src/main/java/cn/quantgroup/xyqb/Constants.java
#	src/main/java/cn/quantgroup/xyqb/controller/external/user/InnerController.java
parents 5e6176c8 5171d7e9
......@@ -8,30 +8,17 @@ public interface Constants {
// zero fill with 4 chars...
String ZERO_FILL_TEMPLATE = "%04d";
String PASSWORD_SALT = "_lkb";
String IMAGE_CAPTCHA_KEY = "img_captcha:";
String REDIS_CAPTCHA_KEY = "auth:";
String CONFIG_CAPTCHA = "cfg_captcha_%";
// app 后端白名单
String CONFIG_CAPTCHA_WHITEIP_LIST = "cfg_captcha_white_ip_appbackend";
// 每个 IP 每分钟 captcha 限制
String CONFIG_CAPTCHA_PERIP_PERMIN = "cfg_captcha_per_ip_per_min";
// 是否启用万能验证码
String CONFIG_CAPTCHA_MAGIC_CODE_ENABLED = "cfg_captcha_magic_code_enabled";
String REDIS_PREFIX_VERIFICATION_CODE = "verificationCode_";
String REDIS_PREFIX_VERIFICATION_VOICE_CODE = "verificationCode_voice_";
//新版短信验证码计数
String REDIS_SMS_CODE_COUNT = "SMS_Phone_verification_code_count:";
String REDIS_SMS_IP_COUNT = "SMS_Ip_verification_code_count:";
String REDIS_SMS_CODE_COUNT = "SMS_Phone_verification_code_count:";
String REDIS_SMS_DEVICE_COUNT = "SMS_Device_verification_code_count:";
//新版语音验证码计数
String REDIS_VOICE_CODE_COUNT = "Voice_Phone_verification_code_count:";
String REDIS_VOICE_IP_COUNT = "Voice_Ip_verification_code_count:";
String REDIS_VOICE_CODE_COUNT = "Voice_Phone_verification_code_count:";
String REDIS_VOICE_DEVICE_COUNT = "Voice_Device_verification_code_count:";
String REDIS_VERIFICATION_COUNT = "verification_code_count:";
......@@ -39,33 +26,51 @@ public interface Constants {
Long VERIFICATION_CODE_FINITE_COUNT = 3L;
String X_AUTH_TOKEN = "x-auth-token";
String ONE_TIME_TOKEN = "oneTimeToken";
// -- Start -- IPV4安全策略常量组
/** 账密不匹配错误 - 按账号计数 */
String REDIS_PASSWORD_ERROR_COUNT = "password_error_count:";
/** 账密不匹配错误 - 按IP计数 */
String REDIS_PASSWORD_ERROR_COUNT_FOR_IPV4 = "password_error_count_4_ipv4:";
/** 账密匹配成功 - 按IP计数 */
String REDIS_PASSWORD_SUCCESS_COUNT_FOR_IPV4 = "password_success_count_4_ipv4:";
/** 账密安全策略 - 白名单 */
String IPV4_LOCK_WHITE = "lock_ipv4:white:";
/** 账密安全策略 - 黑名单 */
String IPV4_LOCK_BLACK = "lock_ipv4:black:";
/** 账密安全策略 - 锁机制自定义参数 - 锁定分钟数 */
String IPV4_LOCK_MINUTES_REDIS = "lock_ipv4:minutes:";
/** 账密安全策略 - 锁机制自定义参数 - 锁开关阈值 */
String IPV4_LOCK_ON_COUNTS_REDIS = "lock_ipv4:on_counts:";
/** 账密安全策略 - 锁机制 - IPV4锁 */
String IPV4_LOCK = "lock_ipv4:";
Long IPV4_LOCK_MINUTES = 3 * 60L;
Long IPV4_COUNT_MINUTES = 1L;
Long IPV4_LOCK_ON_COUNTS = 60L;
/** 账密不匹配错误 - 锁机制默认参数 - 锁定分钟数 */
Long IPV4_FAILED_LOCK_MINUTES = 3 * 60L;
/** 账密不匹配错误 - 锁机制默认参数 - 计数周期 */
Long IPV4_FAILED_COUNT_MINUTES = 1L;
/** 账密不匹配错误 - 锁机制默认参数 - 锁开关阈值 */
Long IPV4_LOCK_ON_FAILED_COUNTS = 60L;
/** 账密匹配成功 - 锁机制默认参数 - 锁定分钟数 */
Long IPV4_SUCCESS_LOCK_MINUTES = 30L;
/** 账密匹配成功 - 锁机制默认参数 - 计数周期 */
Long IPV4_SUCCESS_COUNT_MINUTES = 1 * 60L;
/** 账密匹配成功 - 锁机制默认参数 - 锁开关阈值 */
Long IPV4_LOCK_ON_SUCCESS_COUNTS = 40L;
/** 危险期 - 起始时间(Hour) */
int DANGEROUS_TIME_START = 22;
/** 危险期 - 结束时间(Hour) */
int DANGEROUS_TIME_END = 6;
/** 安全策略参数设置 - 秘钥 - 口令 */
String CLEAR_LOCK_FOR_IPV4 = "x-clear-lock-11241842-y";
/** 安全策略参数设置 - 私钥 */
String CLEAR_LOCK_FOR_IPV4_KEY = "lhp.family.dwy.sjs.yym.cxy.cpg";
// -- End -- IPV4安全策略常量组
/**
* redis中token的key值前缀
*/
String SESSION_PREFIX = "spring:session:sessions:";
Long ONE_DAY = 24 * 60 * 60L;
interface Channel {
long LKB = 1; // 量化派
long JR58 = 175; // 58金融
long BAITIAO = 222L;
String LKB_CODE = "0002"; // 量化派channnel_code
long WECHAT = 198L;
......@@ -78,15 +83,12 @@ public interface Constants {
Long ONE_DAY = 24 * 60 * 60L;
}
interface WeChat {
String APP_ID = "wx0a7c0bce0c3ac523";
String REDIRECT_URL = "http://wechattest.xyqb.com/webchat/receiveCode";
String SCOPE = "snsapi_userinfo";
interface UserAvatar {
String AVATAR_DEFAULT = "https://avatar.xyqb.com/default_avatar.png";
}
interface UserAvatar {
String AVATAR_DEFAULT = "https://avatar.xyqb.com/default_avatar.png";
}
interface Sms {
String VERIFICATION_CODE = "尊敬的用户,您本次的验证码为:%s,有效期10分钟。"; // 随机验证码
String BINDCARD_SMS = "用户您好,您已绑卡成功,将会在1-5个工作日内收到借款,请耐心等待。如有疑问,请致电400-002-0061,感谢您对我们的支持";//绑卡成功后的短信文案
......@@ -94,4 +96,5 @@ public interface Constants {
}
String AES_KEY = "ScnmRBhuQpo9kBdn";
}
package cn.quantgroup.xyqb.aspect.accessable;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.util.IPUtil;
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.beans.factory.annotation.Value;
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;
......@@ -17,15 +22,19 @@ import javax.servlet.http.HttpServletRequest;
import javax.xml.crypto.dsig.keyinfo.PGPData;
/**
* Created by 11 on 2017/3/23.
* IP白名单检验
*
* @author renwc
* @date 2017-12-01
*/
@Aspect
@Component
public class IpValidatorAdvisor {
private static final Logger LOGGER = LoggerFactory.getLogger(IpValidatorAdvisor.class);
public class IpValidateAdvisor {
private static final Logger LOGGER = LoggerFactory.getLogger(IpValidateAdvisor.class);
/*@Value("${config.accessable}")
private boolean accessable;*/
@Autowired
@Qualifier("stringRedisTemplate")
private RedisTemplate<String, String> redisTemplate;
@Pointcut("@annotation(cn.quantgroup.xyqb.aspect.accessable.IpValidator)")
private void needIpValidate() {
......@@ -34,26 +43,18 @@ public class IpValidatorAdvisor {
@Around("needIpValidate()")
private Object doCapchaValidate(ProceedingJoinPoint pjp) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
String ip = IPUtil.getRemoteIP(request);
LOGGER.info("获取ip地址:" + ip);
if(ip.startsWith("172")||ip.startsWith("192")){
// 客户端IP
String clientIp = IPUtil.getRemoteIP(request);
// 白名单
if(IPUtil.whiteOf(clientIp)){
return pjp.proceed();
} else {
LOGGER.error("非法ip:{}", ip);
return JsonResult.buildErrorStateResult("非法ip", null);
}
/*HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String ip = IPUtil.getRemoteIP(request);
if(accessable) {
// 补充白名单
if(redisTemplate.opsForSet().isMember(Constants.IPV4_LOCK_WHITE, clientIp)){
return pjp.proceed();
} else {
if(ip.startsWith("172")){
return pjp.proceed();
} else {
LOGGER.error("非法ip:{}", ip);
return JsonResult.buildErrorStateResult("非法ip", null);
}
}*/
//return pjp.proceed();
}
IPUtil.logIp(LOGGER, request);
LOGGER.error("白名单不匹配拦截:ip={}", clientIp);
return JsonResult.buildErrorStateResult("非法访问", null);
}
}
......@@ -3,7 +3,7 @@ package cn.quantgroup.xyqb.aspect.accessable;
import java.lang.annotation.*;
/**
* Created by 11 on 2017/3/23.
* Ip白名单检验标记
*/
@Documented
@Target(ElementType.METHOD)
......
......@@ -4,6 +4,7 @@ package cn.quantgroup.xyqb.aspect.captcha;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.thirdparty.jcaptcha.AbstractManageableImageCaptchaService;
import cn.quantgroup.xyqb.util.IPUtil;
import cn.quantgroup.xyqb.util.ValidationUtil;
import com.octo.captcha.service.CaptchaServiceException;
import org.apache.commons.codec.binary.Base64;
......@@ -67,7 +68,7 @@ public class CaptchaFiniteValidateAdvisor {
/**
* 限次图形验证码切面
*/
@Pointcut("@annotation(cn.quantgroup.xyqb.aspect.captcha.CaptchaFineteValidator)")
@Pointcut("@annotation(cn.quantgroup.xyqb.aspect.captcha.CaptchaFiniteValidator)")
private void needCaptchaFiniteValidate() {
}
......@@ -89,7 +90,7 @@ public class CaptchaFiniteValidateAdvisor {
String phoneNo = phonePasswordMap.get("phoneNo");
Long countErrorByPhone = getCount(phoneNo);
if(countErrorByPhone == null){
LOGGER.info("用户名或密码不正确, phoneNo={}, countErrorByPhone={}, clientIp={}", phoneNo, countErrorByPhone, request.getRemoteAddr());
LOGGER.info("用户名或密码不正确, phoneNo={}, countErrorByPhone={}, clientIp={}", phoneNo, countErrorByPhone, IPUtil.getRemoteIP(request));
return JsonResult.buildErrorStateResult("用户名或密码不正确", null);
}
if (countErrorByPhone > Constants.Image_Need_Count) {
......@@ -97,7 +98,7 @@ public class CaptchaFiniteValidateAdvisor {
String captchaId = Optional.ofNullable(request.getParameter("captchaId")).orElse("");
String captchaValue = request.getParameter("captchaValue");
if (shouldSkipCaptchaValidate(registerFrom, captchaId, captchaValue)) {
LOGGER.info("使用超级图形验证码校验, registerFrom={}, clientIp={}", registerFrom, request.getRemoteAddr());
LOGGER.info("使用超级图形验证码校验, registerFrom={}, clientIp={}", registerFrom, IPUtil.getRemoteIP(request));
return pjp.proceed();
}
if (StringUtils.isNotBlank(captchaValue)) {
......@@ -149,10 +150,10 @@ public class CaptchaFiniteValidateAdvisor {
}
/**
* 单次令牌参数解析
* 账密参数解析
*
* @param request 当前请求,其首部行必须包含形如【SingleToken 13461067662:0123456789abcdef】的UTF-8编码的Base64加密参数
* @return 令牌参数Map 或 null
* @param request 当前请求
* @return 账密参数Map 或 null
*/
private Map<String, String> getHeaderParam(HttpServletRequest request) {
String verificationHeader = "Basic ";
......
......@@ -11,5 +11,5 @@ import java.lang.annotation.*;
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CaptchaFineteValidator {
public @interface CaptchaFiniteValidator {
}
......@@ -4,6 +4,7 @@ import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.controller.IBaseController;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.thirdparty.jcaptcha.AbstractManageableImageCaptchaService;
import cn.quantgroup.xyqb.util.IPUtil;
import com.octo.captcha.service.CaptchaServiceException;
import java.io.PipedReader;
......@@ -86,15 +87,16 @@ public class CaptchaNewValidateAdvisor {
String captchaValue = request.getParameter("captchaValue");
String phoneNo = request.getParameter("phoneNo");
String deviceId = Optional.ofNullable(request.getParameter("deviceId")).orElse("");
String clientIp = getIp();
String clientIp = IPUtil.getRemoteIP(request);
Long countIP = countIP(clientIp);
Long countPhone = countPhone(phoneNo);
Long countDeviceId = countDeviceId(deviceId);
IPUtil.logIp(LOGGER, request);
LOGGER.info("使用图形验证码, registerFrom={}, clientIp={},手机号次数:{},设备次数:{},ip次数:{},phone:{}", registerFrom, clientIp,countPhone,countDeviceId,countIP,phoneNo);
if (countIP > Constants.Image_Need_Count || countPhone > Constants.Image_Need_Count || countDeviceId > Constants.Image_Need_Count) {
//if (countIP > Constants.Image_Need_Count || countPhone > Constants.Image_Need_Count || countDeviceId > Constants.Image_Need_Count) {
if(true){
if (shouldSkipCaptchaValidate(registerFrom, captchaId, captchaValue)) {
LOGGER.info("使用超级图形验证码校验, registerFrom={}, clientIp={}", registerFrom, request.getRemoteAddr());
LOGGER.info("使用超级图形验证码校验, registerFrom={}, clientIp={}", registerFrom, clientIp);
return pjp.proceed();
}
......@@ -183,25 +185,6 @@ public class CaptchaNewValidateAdvisor {
}
}
private String getIp() {
HttpServletRequest request = getRequest();
String ip = request.getHeader("x-real-ip");
if (StringUtils.isEmpty(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;
}
private HttpServletRequest getRequest() {
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return attrs.getRequest();
......
......@@ -4,6 +4,7 @@ package cn.quantgroup.xyqb.aspect.captcha;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.thirdparty.jcaptcha.AbstractManageableImageCaptchaService;
import cn.quantgroup.xyqb.util.IPUtil;
import com.octo.captcha.service.CaptchaServiceException;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
......@@ -65,7 +66,6 @@ public class CaptchaValidateAdvisor {
private void needCaptchaValidate() {
}
/**
* 在受图形验证码保护的接口方法执行前, 执行图形验证码校验
*
......@@ -81,7 +81,7 @@ public class CaptchaValidateAdvisor {
Object captchaValue = request.getParameter("captchaValue");
if (shouldSkipCaptchaValidate(registerFrom, captchaId, captchaValue)) {
LOGGER.info("使用超级图形验证码校验, registerFrom={}, clientIp={}", registerFrom, request.getRemoteAddr());
LOGGER.info("使用超级图形验证码校验, registerFrom={}, clientIp={}", registerFrom, IPUtil.getRemoteIP(request));
return pjp.proceed();
}
......
......@@ -5,7 +5,7 @@ import cn.quantgroup.xyqb.entity.User;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.model.session.SessionStruct;
import cn.quantgroup.xyqb.session.XyqbSessionContextHolder;
import com.alibaba.fastjson.JSONObject;
import cn.quantgroup.xyqb.util.IPUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
......@@ -32,9 +32,9 @@ import java.util.*;
*/
@Aspect
@Component
public class PasswordFreeAccessValidatorValidateAdvisor {
public class PasswordFreeAccessValidateAdvisor {
private static final Logger LOGGER = LoggerFactory.getLogger(PasswordFreeAccessValidatorValidateAdvisor.class);
private static final Logger LOGGER = LoggerFactory.getLogger(PasswordFreeAccessValidateAdvisor.class);
private static final String PHONE_NO = "phoneNo";
private static final String USER_ID = "userId";
......@@ -56,7 +56,7 @@ public class PasswordFreeAccessValidatorValidateAdvisor {
@Around("passwordFreeAccess()")
private Object checkToken(ProceedingJoinPoint pjp) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
boolean valid = tokenValid(request) || ipValid(request);
boolean valid = tokenValid(request);
if (valid) {
return pjp.proceed();
}
......@@ -70,59 +70,42 @@ public class PasswordFreeAccessValidatorValidateAdvisor {
*/
private boolean tokenValid(HttpServletRequest request) {
Objects.requireNonNull(request, "无效请求");
String clientIp = IPUtil.getRemoteIP(request);
Set<String> paramKeys = request.getParameterMap().keySet();
if(!paramKeys.contains(PHONE_NO) && !paramKeys.contains(USER_ID)){
LOGGER.info("非法请求 - 缺少参数, paramKeys={}, clientIp={}", paramKeys, request.getRemoteAddr());
LOGGER.info("非法请求 - 缺少参数, paramKeys={}, clientIp={}", paramKeys, clientIp);
return false;
}
// 当前请求的phoneNo/userId
String phoneNo = request.getParameter(PHONE_NO);
String userId = request.getParameter(USER_ID);
if(StringUtils.isBlank(phoneNo) && StringUtils.isBlank(userId)){
LOGGER.info("非法请求 - 缺少参数, phoneNo={}, userId={}, clientIp={}", phoneNo, userId, request.getRemoteAddr());
LOGGER.info("非法请求 - 缺少参数, phoneNo={}, userId={}, clientIp={}", phoneNo, userId, clientIp);
return false;
}
// 当前请求的Token
String token = request.getHeader(Constants.X_AUTH_TOKEN);
if (Objects.isNull(token) || token.length() != 36) {
LOGGER.info("非法请求 - 无效token, token={}, phoneNo={}, userId={}, clientIp={}", token, phoneNo, userId, request.getRemoteAddr());
LOGGER.info("非法请求 - 无效token, token={}, phoneNo={}, userId={}, clientIp={}", token, phoneNo, userId, clientIp);
return false;
}
// 当前session
SessionStruct session = XyqbSessionContextHolder.getXSessionFromRedis(token);
if (Objects.isNull(session) || Objects.isNull(session.getValues()) || Objects.isNull(session.getValues().getUser())){
LOGGER.info("非法请求 - 未登录, token={}, phoneNo={}, userId={}, clientIp={}", token, phoneNo, userId, request.getRemoteAddr());
LOGGER.info("非法请求 - 未登录, token={}, phoneNo={}, userId={}, clientIp={}", token, phoneNo, userId, clientIp);
return false;
}
// 当前用户
User user = session.getValues().getUser();
if(Objects.isNull(user.getId()) && StringUtils.isBlank(user.getPhoneNo())){
LOGGER.info("非法请求 - 未登录, token={}, phoneNo={}, userId={}, clientIp={}", token, phoneNo, userId, request.getRemoteAddr());
LOGGER.info("非法请求 - 未登录, token={}, phoneNo={}, userId={}, clientIp={}", token, phoneNo, userId, clientIp);
return false;
}
// 校对用户信息是否匹配
boolean valid = (Objects.nonNull(user.getId()) && Objects.equals(userId, user.getId().toString()));
valid = valid || (StringUtils.isNotBlank(phoneNo) && Objects.equals(phoneNo, user.getPhoneNo()));
if(!valid) {
LOGGER.info("非法请求 - 身份不匹配, token={}, phoneNo=({},{}), userId=({},{}), clientIp={}", token, phoneNo, user.getPhoneNo(), userId, user.getId(), request.getRemoteAddr());
}
return valid;
}
/**
* 校验免密访问
* 规则:来访IP与白名单匹配
* @return True or False
*/
private boolean ipValid(HttpServletRequest request) {
Objects.requireNonNull(request, "无效请求");
String remoteAddr = request.getRemoteAddr();
Set<String> whiteAddr = Collections.emptySet();
// Todo 配置白名单
// 校对来访IP是否与白名单匹配
boolean valid = StringUtils.isNotBlank(remoteAddr) && whiteAddr.contains(remoteAddr);
if(!valid) {
LOGGER.info("非法请求 - 未授权访问, clientIp={}", request.getRemoteAddr());
LOGGER.info("非法请求 - 身份不匹配, token={}, phoneNo=({},{}), userId=({},{}), clientIp={}", token, phoneNo, user.getPhoneNo(), userId, user.getId(), clientIp);
}
return valid;
}
......
......@@ -3,6 +3,7 @@ package cn.quantgroup.xyqb.aspect.lock;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.util.IPUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
......@@ -39,7 +40,7 @@ public class PasswordErrorFiniteValidateAdvisor {
/**
* 密码错误限次切面
*/
@Pointcut("@annotation(cn.quantgroup.xyqb.aspect.lock.PasswordFineteValidator)")
@Pointcut("@annotation(cn.quantgroup.xyqb.aspect.lock.PasswordErrorFiniteValidator)")
private void passwordErrorFiniteValidate() {
}
......@@ -58,23 +59,29 @@ public class PasswordErrorFiniteValidateAdvisor {
}*/
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 客户端IP
String clientIp = getIp(request);
// 入口服务器IP
if(StringUtils.startsWith(clientIp, "139.198.")){
String clientIp = IPUtil.getRemoteIP(request);
// 白名单
if(IPUtil.whiteOf(clientIp)){
return pjp.proceed();
}
// 白名单
// 补充白名单
if(redisTemplate.opsForSet().isMember(Constants.IPV4_LOCK_WHITE, clientIp)){
return pjp.proceed();
}
// 入口服务器IP
if(StringUtils.startsWith(clientIp, "139.198.")){
return pjp.proceed();
}
// 黑名单
if(redisTemplate.opsForSet().isMember(Constants.IPV4_LOCK_BLACK, clientIp)){
IPUtil.logIp(LOGGER, request);
LOGGER.info("Lock_ipv4: locked ip access:{}", clientIp);
return JsonResult.buildErrorStateResult("登录失败", null);
}
String lockIpv4Key = getLockIpv4Key(clientIp);
String lock = redisTemplate.opsForValue().get(lockIpv4Key);
if (Objects.equals(Boolean.TRUE.toString(), lock)){
IPUtil.logIp(LOGGER, request);
LOGGER.info("Lock_ipv4: locked ip access:{}", clientIp);
return JsonResult.buildErrorStateResult("登录失败", null);
}
......@@ -85,30 +92,4 @@ public class PasswordErrorFiniteValidateAdvisor {
return Constants.IPV4_LOCK + 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;
}
}
......@@ -11,5 +11,5 @@ import java.lang.annotation.*;
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PasswordFineteValidator {
public @interface PasswordErrorFiniteValidator {
}
package cn.quantgroup.xyqb.aspect.token;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.model.JsonResult;
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.util.Objects;
/**
* 一次性令牌校验切面
*
* @author 任文超
* @version 1.0.0
* @since 2017-10-31
*/
@Aspect
@Component
public class OneTimeTokenValidateAdvisor {
private static final Logger LOGGER = LoggerFactory.getLogger(OneTimeTokenValidateAdvisor.class);
@Autowired
@Qualifier("stringRedisTemplate")
private RedisTemplate<String, String> redisTemplate;
/**
* 一次性令牌校验切面
*/
@Pointcut("@annotation(cn.quantgroup.xyqb.aspect.token.OneTimeTokenValidator)")
private void needOneTimeToken() {}
/**
* 在受一次性令牌保护的接口方法执行前, 执行一次性令牌校验
*
* @throws Throwable
*/
@Around("needOneTimeToken()")
private Object checkOneTimeToken(ProceedingJoinPoint pjp) throws Throwable {
boolean valid = oneTimeTokenValid();
if (valid) {
return pjp.proceed();
}
return JsonResult.buildSuccessResult("操作失败,请重新提交", "", 2L);
}
/**
* 校验一次性令牌
* @return True or False
*/
private boolean oneTimeTokenValid() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 当前请求的OneTimeToken
String oneTimeToken = request.getParameter(Constants.ONE_TIME_TOKEN);
if (StringUtils.isBlank(oneTimeToken)){
return false;
}
String oneTimeToken_value = redisTemplate.opsForValue().get(oneTimeToken);
// OneTimeToken再Redis中值不应为空值(空白、空格、null)
if (StringUtils.isBlank(oneTimeToken_value)) {
return false;
}
boolean valid = Objects.equals(Boolean.TRUE.toString(), oneTimeToken_value);
// OneTimeToken校验正确时删除key
if(valid) {
redisTemplate.delete(oneTimeToken);
}else {
LOGGER.info("令牌已失效,请重新请求, oneTimeToken={}, clientIp={}", oneTimeToken, request.getRemoteAddr());
}
return valid;
}
}
package cn.quantgroup.xyqb.aspect.token;
import java.lang.annotation.*;
/**
* 一次性令牌校验标记
* @author 任文超
* @version 1.0.0
* @since 2017-10-31
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OneTimeTokenValidator {
}
......@@ -2,6 +2,7 @@ package cn.quantgroup.xyqb.controller;
import cn.quantgroup.xyqb.entity.User;
import cn.quantgroup.xyqb.model.session.SessionStruct;
import cn.quantgroup.xyqb.util.IPUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
......@@ -50,25 +51,7 @@ public interface IBaseController {
default String getIp() {
HttpServletRequest request = getRequest();
String ip = request.getHeader("x-real-ip");
if (StringUtils.isEmpty(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;
return IPUtil.getRemoteIP(request);
}
default String getProtocol() {
......
......@@ -30,7 +30,6 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import sun.management.counter.LongCounter;
/**
* 类名称:ImgCaptchaController
......
......@@ -76,12 +76,6 @@ public class LockIpv4Controller implements IBaseController {
// 操作标记
boolean lock = Objects.equals(Boolean.TRUE.toString(), act);
boolean valid = ValidationUtil.isValid(key, lock);
// Todo - 兼容简单验证规则,暂时保留
if(!valid){
Calendar now = Calendar.getInstance();
int hour = now.get(Calendar.HOUR_OF_DAY);
valid = Objects.equals(Constants.CLEAR_LOCK_FOR_IPV4_KEY+hour, key);
}
if(valid){
lockIpv4(ip, lock);
return JsonResult.buildSuccessResult("Success",null);
......@@ -101,19 +95,19 @@ public class LockIpv4Controller implements IBaseController {
String lockIpv4Key = getLockIpv4Key(ip);
if(lock){
// 每分钟计数阈值
long counts = Constants.IPV4_LOCK_ON_COUNTS;
long counts = Constants.IPV4_LOCK_ON_FAILED_COUNTS;
String redisCounts = redisTemplate.opsForValue().get(Constants.IPV4_LOCK_ON_COUNTS_REDIS);
if(StringUtils.isNumeric(redisCounts) && Integer.valueOf(redisCounts) > 0){
counts = Integer.valueOf(redisCounts);
}
// 锁定时长
long minutes = Constants.IPV4_LOCK_MINUTES;
long minutes = Constants.IPV4_FAILED_LOCK_MINUTES;
String redisMinutes = redisTemplate.opsForValue().get(Constants.IPV4_LOCK_ON_COUNTS_REDIS);
if(StringUtils.isNumeric(redisMinutes) && Integer.valueOf(redisMinutes) > 0){
minutes = Integer.valueOf(redisMinutes);
}
redisTemplate.opsForValue().set(lockIpv4Key, Boolean.TRUE.toString(), minutes, TimeUnit.MINUTES);
LOGGER.info("Lock_ipv4: locked ip access:{}, error overstep {} times in {} minutes, do lock {} minutes", ip, counts, Constants.IPV4_COUNT_MINUTES, minutes);
LOGGER.info("Lock_ipv4: locked ip access:{}, error overstep {} times in {} minutes, do lock {} minutes", ip, counts, Constants.IPV4_FAILED_COUNT_MINUTES, minutes);
}else{
redisTemplate.delete(lockIpv4Key);
LOGGER.info("Lock_ipv4: unlocked ip Success. ip:{}", ip);
......@@ -239,7 +233,7 @@ public class LockIpv4Controller implements IBaseController {
}else{
redisTemplate.delete(Constants.IPV4_LOCK_ON_COUNTS_REDIS);
redisTemplate.delete(Constants.IPV4_LOCK_MINUTES_REDIS);
LOGGER.info("Lock_ipv4: remove redis-param counts、minutes Success, counts:{},minutes:{}, current default:[counts:{},minutes:{}]", Constants.IPV4_LOCK_ON_COUNTS, Constants.IPV4_LOCK_MINUTES);
LOGGER.info("Lock_ipv4: remove redis-param counts、minutes Success, counts:{},minutes:{}, current default:[counts:{},minutes:{}]", Constants.IPV4_LOCK_ON_FAILED_COUNTS, Constants.IPV4_FAILED_LOCK_MINUTES);
}
}
......
......@@ -83,8 +83,7 @@ public class AppController implements IBaseController {
return JsonResult.buildErrorStateResult(USER_ERROR_OR_PASSWORD_ERROR, null);
}
String requestIp = IPUtil.getRemoteIP(request);
LOGGER.info("app/login第三方用户登录, loginFrom:{},channelId:{},btRegisterChannelId:{} requestIp:{},idNo:{},name:{}", registerFrom,channelId,btRegisterChannelId, requestIp,idNo,name);
LOGGER.info("app/login第三方用户登录, loginFrom:{},channelId:{},btRegisterChannelId:{} requestIp:{},idNo:{},name:{}", registerFrom,channelId,btRegisterChannelId, IPUtil.getRemoteIP(request),idNo,name);
User user = userService.findByPhoneInDb(phoneNo);
if (user == null) {
user = register(registerFrom, phoneNo, idNo, name, channelId,btRegisterChannelId);
......@@ -129,8 +128,7 @@ public class AppController implements IBaseController {
return JsonResult.buildErrorStateResult(USER_ERROR_OR_PASSWORD_ERROR, null);
}
String requestIp = IPUtil.getRemoteIP(request);
LOGGER.info("第三方用户登录, loginFrom:{},channelId:{}, requestIp:{}", registerFrom,channelId, requestIp);
LOGGER.info("第三方用户登录, loginFrom:{},channelId:{}, requestIp:{}", registerFrom,channelId, IPUtil.getRemoteIP(request));
User user = userService.findByPhoneInDb(phoneNo);
if (user == null) {
......@@ -168,8 +166,7 @@ public class AppController implements IBaseController {
return JsonResult.buildErrorStateResult(USER_ERROR_OR_PASSWORD_ERROR, null);
}
String requestIp = IPUtil.getRemoteIP(request);
LOGGER.info("app/login_super第三方用户登录, loginFrom:{},channelId:{},btRegisterChannelId:{} requestIp:{},idNo:{},name:{}", registerFrom,channelId,btRegisterChannelId, requestIp,idNo,name);
LOGGER.info("app/login_super第三方用户登录, loginFrom:{},channelId:{},btRegisterChannelId:{} requestIp:{},idNo:{},name:{}", registerFrom,channelId,btRegisterChannelId, IPUtil.getRemoteIP(request),idNo,name);
User user = userService.findByPhoneInDb(phoneNo);
if (user == null) {
user = register(registerFrom, phoneNo, idNo, name, channelId,btRegisterChannelId);
......
package cn.quantgroup.xyqb.controller.external.user;
import cn.quantgroup.user.enums.Relation;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.aspect.limit.PasswordFreeAccessValidator;
import cn.quantgroup.xyqb.aspect.accessable.IpValidator;
import cn.quantgroup.xyqb.aspect.logcaller.LogHttpCaller;
import cn.quantgroup.xyqb.controller.IBaseController;
import cn.quantgroup.xyqb.entity.*;
......@@ -426,8 +427,7 @@ public class InnerController implements IBaseController {
return JsonResult.buildErrorStateResult("修改联系人不存在", null);
}
contact = contactService.saveContact(name, phoneNo, relation, contact);
String ip= IPUtil.getRemoteIP(request);
LOGGER.info("修改后联系人信息:{},修改原因:{},操作ip:{}",contact,reason,ip);
LOGGER.info("修改后联系人信息:{},修改原因:{},操作ip:{}",contact,reason,IPUtil.getRemoteIP(request));
return JsonResult.buildSuccessResult("修改联系人成功", contact);
}
......@@ -524,9 +524,9 @@ public class InnerController implements IBaseController {
return JsonResult.buildSuccessResult(null, UserExtInfoRet.getUserExtInfoRet(info));
}
// Todo @PasswordFreeAccessValidator
@RequestMapping("/user_detail/search_list")
@IpValidator
@LogHttpCaller
@RequestMapping("/user_detail/search_list")
public JsonResult searchUserDetailList(String name, String phoneNo, String idNo) {
LOGGER.info("searchUserDetailList ,param.name:{},phone:{},idNo:{},ip:{}", name, phoneNo, idNo, getIp());
......@@ -578,9 +578,9 @@ public class InnerController implements IBaseController {
return JsonResult.buildSuccessResult("success", wechatUserInfo.getOpenId());
}
// Todo @PasswordFreeAccessValidator
@RequestMapping("/user-association/search/phone")
@IpValidator
@LogHttpCaller
@RequestMapping("/user-association/search/phone")
public JsonResult findUserAssociationByPhone(String phoneNo) {
UserDetail userDetail = userDetailService.findByPhoneNo(phoneNo);
UserAssociation bean = new UserAssociation();
......@@ -874,7 +874,7 @@ public class InnerController implements IBaseController {
String district,
String address,
String contacts) {
LOGGER.info("保存用户、联系人、地址信息:registeredFrom:[{}], phoneNo[{}],name[{}],idNo[{}],provinceCode[{}],cityCode[{}],districtCode[{}]," +
LOGGER.info("[/innerapi/user/save_multi]保存用户、联系人、地址信息:registeredFrom:[{}], phoneNo[{}],name[{}],idNo[{}],provinceCode[{}],cityCode[{}],districtCode[{}]," +
"address[{}],contacts[{}]", registeredFrom, phoneNo, name, idNo, provinceCode, cityCode, districtCode, address, contacts);
if (!NumberUtils.isDigits(registeredFrom)) {
......@@ -936,6 +936,7 @@ public class InnerController implements IBaseController {
if (!ValidationUtil.validatePhoneNo(phoneNo)) {
return JsonResult.buildErrorStateResult("手机号不正确", null);
}
LOGGER.info("[/innerapi/user/register]channelId={},ip={}", channelId, getIp());
User user = userService.findByPhoneInDb(phoneNo);
if (user == null) {
user = userService.registerAndReturn(phoneNo, StringUtils.defaultString(password, ""), channelId);
......
......@@ -232,40 +232,43 @@ public class SmsController implements IBaseController {
if (!ValidationUtil.validatePhoneNo(phoneNo)) {
return JsonResult.buildErrorStateResult("手机号格式有误", null);
}
String verificationPhoneCountKey = Constants.REDIS_SMS_CODE_COUNT + phoneNo;
Long getPhoneVerificationCount = redisTemplate.opsForHash().increment(verificationPhoneCountKey, Constants.REDIS_SMS_CODE_COUNT, 1);
redisTemplate.expire(verificationPhoneCountKey, DateUtils.getSeconds(), TimeUnit.SECONDS);
String clientIp = getIp();
LOGGER.info("请求短信新版本接口:phoneNo:{},deviceId:{},IP:{}",phoneNo,deviceId,clientIp);
// 手机号计数器
Long getPhoneVerificationCount = redisTemplate.opsForHash().increment(Constants.REDIS_SMS_CODE_COUNT, phoneNo, 1);
redisTemplate.expire(Constants.REDIS_SMS_CODE_COUNT, DateUtils.getSeconds(), TimeUnit.SECONDS);
// 设备号计数器
Long getDeviceVerificationCount = 0L;
if (StringUtils.isNotBlank(deviceId)) {
getDeviceVerificationCount = redisTemplate.opsForHash().increment(Constants.REDIS_SMS_DEVICE_COUNT, deviceId, 1);
redisTemplate.expire(Constants.REDIS_SMS_DEVICE_COUNT, DateUtils.getSeconds(), TimeUnit.SECONDS);
}
// IP计数器
Long getIPVerificationCount = 0L;
if (StringUtils.isNotBlank(clientIp)) {
getIPVerificationCount = redisTemplate.opsForHash().increment(Constants.REDIS_SMS_IP_COUNT, clientIp, 1);
redisTemplate.expire(Constants.REDIS_SMS_IP_COUNT, DateUtils.getSeconds(), TimeUnit.SECONDS);
}
// 手机号上限检查
if (getPhoneVerificationCount > PHONE_MAX_PER_DAY) {
LOGGER.info("您手机号已经达到获取今天短信验证码上限:phoneNo:{},deviceId:{},ip:{}",phoneNo,deviceId,getIp());
LOGGER.info("您手机号已经达到获取今天短信验证码上限:phoneNo:{},count:{}", phoneNo, getPhoneVerificationCount);
return JsonResult.buildErrorStateResult("今天已获取20次短信验证码,请使用语音验证码或明天再试", null);
}
String verificationIPCountKey = getIp();
// Todo - 运维解决真实IP获取问题后,打开这段代码,实现按IP限制短信验证码获取量
// Todo - 另:当前的计数器计数方式为乐观累加,而且还是提前计数,会导致边界值问题,即临界次数会提前一次记满,并且在后续请求到达时计数器会继续计数
/*
if (!StringUtils.isEmpty(verificationIPCountKey)) {
verificationIPCountKey=Constants.REDIS_SMS_IP_COUNT+verificationIPCountKey;
Long getIPVerificationCount = redisTemplate.opsForHash().increment(verificationIPCountKey, Constants.REDIS_SMS_IP_COUNT, 1);
if (getIPVerificationCount > IP_MAX_PER_DAY) {
return JsonResult.buildErrorStateResult("您当前ip已经达到获取今天验证码上限", null);
}
}*/
LOGGER.info("请求短信新版本接口:phoneNo:{},deviceId:{},IP:{}",phoneNo,deviceId,getIp());
if (!StringUtils.isEmpty(deviceId)) {
String verificationDeviceCountKey = Constants.REDIS_SMS_DEVICE_COUNT + deviceId;
Long getDeviceVerificationCount = redisTemplate.opsForHash().increment(verificationDeviceCountKey, Constants.REDIS_SMS_DEVICE_COUNT, 1);
redisTemplate.expire(verificationDeviceCountKey, DateUtils.getSeconds(), TimeUnit.SECONDS);
if (getDeviceVerificationCount > DEVICE_MAX_PER_DAY) {
LOGGER.info("您设备已经达到获取今天短信验证码上限:phoneNo:{},deviceId:{},ip:{}",phoneNo,verificationDeviceCountKey,getIp());
return JsonResult.buildErrorStateResult("您设备已经达到获取今天短信验证码上限", null);
}
// 设备号上限检查
if (getDeviceVerificationCount > DEVICE_MAX_PER_DAY) {
LOGGER.info("您设备已经达到获取今天验证码上限:deviceId:{},count:{}", deviceId, getDeviceVerificationCount);
return JsonResult.buildErrorStateResult("您设备已经达到获取今天验证码上限", null);
}
// IP上限检查
if (getIPVerificationCount > IP_MAX_PER_DAY) {
LOGGER.info("您当前ip已经达到获取今天短信验证码上限:ip:{},count:{}", clientIp, getIPVerificationCount);
return JsonResult.buildErrorStateResult("您当前ip已经达到获取今天短信验证码上限", null);
}
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
long expire = redisTemplate.getExpire(key, TimeUnit.MINUTES);
if (expire >= EXPIRE_MINUTES - 1) {
LOGGER.info("sendVerificationCode2New1分钟内不能重复获取验证码:phoneNo:{},deviceId:{},ip:{}",phoneNo,deviceId,getIp());
LOGGER.info("sendVerificationCode2New1分钟内不能重复获取验证码:phoneNo:{},deviceId:{},ip:{}",phoneNo,deviceId,clientIp);
return JsonResult.buildErrorStateResult("1分钟内不能重复获取验证码", null);
}
String randomCode = smsIsDebug ? "0000" : String.valueOf(random.nextInt(8999) + 1000);
......@@ -276,14 +279,15 @@ public class SmsController implements IBaseController {
try {
smsService.getSmsSender().sendMsg(message);
redisTemplate.opsForValue().set(key, uniqueId + ":" + randomCode, EXPIRE_MINUTES, TimeUnit.MINUTES);
deleteRetSendCode(phoneNo);//删除用户重置密码,多次错误逻辑
if(isApp && needImageVlidate(verificationIPCountKey,deviceId,phoneNo)){
//删除用户重置密码,多次错误逻辑
deleteRetSendCode(phoneNo);
if(isApp && needImageVlidate(clientIp,deviceId,phoneNo)){
return JsonResult.buildSuccessResult("发送成功", uniqueId,0003L);
}
LOGGER.info("sendVerificationCode2New获取短信成功:phone:{},deviceId:{},ip:{}",phoneNo,deviceId,getIp());
LOGGER.info("sendVerificationCode2New获取短信成功:phone:{},deviceId:{},ip:{}",phoneNo,deviceId,clientIp);
return JsonResult.buildSuccessResult("发送成功", uniqueId);
} catch (Exception e) {
LOGGER.error("发送短信验证码失败:phone:{},deviceId:{},ip:{}",phoneNo,deviceId,getIp());
LOGGER.error("发送短信验证码失败:phone:{},deviceId:{},ip:{}",phoneNo,deviceId,clientIp);
return JsonResult.buildErrorStateResult("发送失败", null);
}
}
......@@ -292,35 +296,48 @@ public class SmsController implements IBaseController {
* 新版本语音验证码
*/
private JsonResult sendVerificationCode2VoiceNew(String phoneNo, String usage, String deviceId) {
String verificationCountKey = Constants.REDIS_VOICE_CODE_COUNT + phoneNo;
Long getVerificationCount = redisTemplate.opsForHash().increment(verificationCountKey, usage.toString(), 1);
if (!ValidationUtil.validatePhoneNo(phoneNo)) {
return JsonResult.buildErrorStateResult("手机号格式有误", null);
}
if (StringUtils.isBlank(usage)) {
return JsonResult.buildErrorStateResult("usage参数无效", null);
}
String clientIp = getIp();
LOGGER.info("请求短信新版本接口:phoneNo:{},deviceId:{},usage:{},IP:{}",phoneNo,deviceId,usage,clientIp);
// 手机号计数器
String verificationCountKey = Constants.REDIS_VOICE_CODE_COUNT + usage;
Long getPhoneVerificationCount = redisTemplate.opsForHash().increment(verificationCountKey, phoneNo, 1);
redisTemplate.expire(verificationCountKey, DateUtils.getSeconds(), TimeUnit.SECONDS);
if (getVerificationCount > PHONE_VOICE_MAX_PER_DAY) {
// 设备号计数器
Long getDeviceVerificationCount = 0L;
if (StringUtils.isNotBlank(deviceId)) {
String verificationDeviceCountKey = Constants.REDIS_VOICE_DEVICE_COUNT + deviceId;
getDeviceVerificationCount = redisTemplate.opsForHash().increment(Constants.REDIS_VOICE_DEVICE_COUNT, verificationDeviceCountKey, 1);
redisTemplate.expire(Constants.REDIS_VOICE_DEVICE_COUNT, DateUtils.getSeconds(), TimeUnit.SECONDS);
}
// IP计数器
Long getIPVerificationCount = 0L;
if (StringUtils.isNotBlank(clientIp)) {
String verificationIPCountKey = Constants.REDIS_VOICE_IP_COUNT+clientIp;
getIPVerificationCount = redisTemplate.opsForHash().increment(Constants.REDIS_VOICE_IP_COUNT, verificationIPCountKey, 1);
redisTemplate.expire(Constants.REDIS_VOICE_IP_COUNT, DateUtils.getSeconds(), TimeUnit.SECONDS);
}
// 手机号上限检查
if (getPhoneVerificationCount > PHONE_VOICE_MAX_PER_DAY) {
LOGGER.info("您手机号已经达到获取今天语音验证码,请使用短信验证码或明天再试:phoneNo:{},count:{}", phoneNo, getPhoneVerificationCount);
return JsonResult.buildErrorStateResult("今天已获取5次语音验证码,请使用短信验证码或明天再试", null);
}
String verificationIPCountKey = getIp();
/* if (!StringUtils.isEmpty(verificationIPCountKey)) {
verificationIPCountKey=Constants.REDIS_VOICE_IP_COUNT+verificationIPCountKey;
Long getIPVerificationCount = redisTemplate.opsForHash().increment(verificationIPCountKey, Constants.REDIS_VOICE_IP_COUNT, 1);
if (getIPVerificationCount > IP_MAX_PER_DAY) {
return JsonResult.buildErrorStateResult("您当前ip已经达到获取今天语音验证码上限", null);
}
}*/
if (!StringUtils.isEmpty(deviceId)) {
String verificationDeviceCountKey = Constants.REDIS_VOICE_DEVICE_COUNT + deviceId;
Long getDeviceVerificationCount = redisTemplate.opsForHash().increment(verificationDeviceCountKey, Constants.REDIS_VOICE_DEVICE_COUNT, 1);
redisTemplate.expire(verificationDeviceCountKey, DateUtils.getSeconds(), TimeUnit.SECONDS);
if (getDeviceVerificationCount > DEVICE_MAX_PER_DAY) {
return JsonResult.buildErrorStateResult("您设备已经达到获取今天语音验证码上限", null);
}
// 设备号上限检查
if (getDeviceVerificationCount > DEVICE_MAX_PER_DAY) {
LOGGER.info("您设备已经达到获取今天语音验证码上限:deviceId:{},count:{}", deviceId, getDeviceVerificationCount);
return JsonResult.buildErrorStateResult("您设备已经达到获取今天语音验证码上限", null);
}
if (!ValidationUtil.validatePhoneNo(phoneNo)) {
return JsonResult.buildErrorStateResult("手机号格式有误", null);
// IP上限检查
if (getIPVerificationCount > IP_MAX_PER_DAY) {
LOGGER.info("您当前ip已经达到获取今天短信验证码上限:ip:{},count:{}", clientIp, getIPVerificationCount);
return JsonResult.buildErrorStateResult("您当前ip已经达到获取今天语音验证码上限", null);
}
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
long expire = redisTemplate.getExpire(key, TimeUnit.MINUTES);
if (expire >= EXPIRE_MINUTES - 1) {
......@@ -333,7 +350,7 @@ public class SmsController implements IBaseController {
smsService.getSmsSender().sendMsg(message);
redisTemplate.opsForValue().set(key, uniqueId + ":" + randomCode, EXPIRE_MINUTES, TimeUnit.MINUTES);
deleteRetSendCode(phoneNo);//删除用户重置密码,多次错误逻辑
if(needImageVlidate(verificationIPCountKey,deviceId,phoneNo)){
if(needImageVlidate(clientIp,deviceId,phoneNo)){
return JsonResult.buildSuccessResult("发送成功", uniqueId,0003L);
}
......
package cn.quantgroup.xyqb.controller.internal.token;
import cn.quantgroup.xyqb.controller.IBaseController;
import cn.quantgroup.xyqb.model.JsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 发放一次性令牌
*
* @author 任文超
* @version 1.0.0
* @since 2017-10-31
*/
@RestController
@RequestMapping("/token")
public class OneTimeTokenController implements IBaseController {
private static final Long SECONDS = 5 * 60L;
@Autowired
@Qualifier("stringRedisTemplate")
private RedisTemplate<String, String> redisTemplate;
/**
* 发放一枚一次性令牌
* One-Time Token用法:http请求首部行添加参数{One-Time-Token 6ac0665b-e19c-4392-a244-7b61690833f5}
*
* @return 一次性令牌
*/
@RequestMapping(value = "/oneTime")
public JsonResult oneTimeToken() {
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(token, Boolean.TRUE.toString(), SECONDS, TimeUnit.SECONDS);
return JsonResult.buildSuccessResult("", token);
}
}
package cn.quantgroup.xyqb.controller.internal.user;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.aspect.captcha.CaptchaFineteValidator;
import cn.quantgroup.xyqb.aspect.lock.PasswordFineteValidator;
import cn.quantgroup.xyqb.aspect.captcha.CaptchaFiniteValidator;
import cn.quantgroup.xyqb.aspect.lock.PasswordErrorFiniteValidator;
import cn.quantgroup.xyqb.aspect.logcaller.LogHttpCaller;
import cn.quantgroup.xyqb.controller.IBaseController;
import cn.quantgroup.xyqb.entity.Merchant;
......@@ -14,16 +14,15 @@ import cn.quantgroup.xyqb.exception.UserNotExistException;
import cn.quantgroup.xyqb.exception.VerificationCodeErrorException;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.model.UserModel;
import cn.quantgroup.xyqb.model.UserRegisterMqMessage;
import cn.quantgroup.xyqb.model.UserStatistics;
import cn.quantgroup.xyqb.service.merchant.IMerchantService;
import cn.quantgroup.xyqb.service.session.ISessionService;
import cn.quantgroup.xyqb.service.sms.ISmsService;
import cn.quantgroup.xyqb.service.user.ILockIpv4Service;
import cn.quantgroup.xyqb.service.user.IUserDetailService;
import cn.quantgroup.xyqb.service.user.IUserService;
import cn.quantgroup.xyqb.service.wechat.IWechatService;
import cn.quantgroup.xyqb.util.*;
import cn.quantgroup.xyqb.util.encrypt.MD5Util;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
......@@ -56,6 +55,9 @@ public class UserController implements IBaseController {
@Autowired
private IUserService userService;
@Autowired
private ILockIpv4Service lockIpv4Service;
@Autowired
@Qualifier("stringRedisTemplate")
private RedisTemplate<String, String> stringRedisTemplate;
......@@ -83,56 +85,12 @@ public class UserController implements IBaseController {
public JsonResult test() {
HttpServletRequest request = getRequest();
String remoteAddr = request.getRemoteAddr();
String xRealIp = request.getHeader("x-real-ip");
String xRealIp = IPUtil.getRemoteIP(request);
String xOriginalClientIp = request.getHeader("x-original-client-ip");
LOGGER.info("Test ips:[client={}, old={}, new={}]", remoteAddr, xRealIp, xOriginalClientIp);
return JsonResult.buildSuccessResult("", getCurrentUserFromRedis());
}
/**
* 解锁特定IP
* @param ip - 目标IP
* @param act - 操作标记,true-lock,false-unlock
* @param key - 密令
* @param request
* @return
*/
@RequestMapping("/lock_ipv4")
public JsonResult clearOrLockIpv4(@RequestParam(required = true)String ip,
@RequestParam(required = false)String act,
@RequestParam(required = true)String key,
HttpServletRequest request) {
if(!ValidationUtil.validateIpv4(ip) || StringUtils.isBlank(act) || StringUtils.isBlank(key)){
LOGGER.info("Fail to clear_or_lock ip:{}", ip);
return JsonResult.buildErrorStateResult("Are you a robot monkey?(^_^)",null);
}
// 操作标记
boolean lock = Objects.equals(Boolean.TRUE.toString(), act);
String header_key = request.getHeader(Constants.IPV4_LOCK.replace(":", ""));
if(Objects.equals(Constants.CLEAR_LOCK_FOR_IPV4, header_key)){
return JsonResult.buildErrorStateResult("Are you a robot monkey?(^_^)", ValidationUtil.getMd5Key(lock));
}
boolean valid = ValidationUtil.isValid(key, lock);
if(!valid){
Calendar now = Calendar.getInstance();
int hour = now.get(Calendar.HOUR_OF_DAY);
valid = Objects.equals(Constants.CLEAR_LOCK_FOR_IPV4_KEY+hour, key);
}
if(valid){
String lockIpv4Key = getLockIpv4Key(ip);
if(lock){
stringRedisTemplate.opsForValue().set(lockIpv4Key, Boolean.TRUE.toString(), Constants.IPV4_LOCK_MINUTES, TimeUnit.MINUTES);
LOGGER.info("Locked ip access:{}, error overstep {} times in {} minutes, do lock {} minutes", ip, Constants.IPV4_LOCK_ON_COUNTS, Constants.IPV4_COUNT_MINUTES, Constants.IPV4_LOCK_MINUTES);
}else{
stringRedisTemplate.delete(lockIpv4Key);
LOGGER.info("Clear_or_lock ip Success:{}", ip);
}
return JsonResult.buildSuccessResult("Success",null);
}
LOGGER.info("Fail to clear_or_lock ip:{}", ip);
return JsonResult.buildErrorStateResult("Are you a robot monkey?(^_^)",null);
}
/**
* 登录(账号 + 密码)
* 密码错误达到限定次数时执行图形验证码校验
......@@ -148,7 +106,7 @@ public class UserController implements IBaseController {
* @param dimension
* @return
*/
@CaptchaFineteValidator
@CaptchaFiniteValidator
@RequestMapping("/loginV1")
public JsonResult loginV1(
@RequestParam(required = false, defaultValue = "1") Long channelId, String appChannel,
......@@ -175,7 +133,6 @@ public class UserController implements IBaseController {
* @param request
* @return
*/
// Todo @OneTimeTokenValidator
@RequestMapping("/login/fastV1")
public JsonResult loginFastV1(
@RequestParam(required = false, defaultValue = "1") Long channelId, String appChannel,
......@@ -188,7 +145,7 @@ public class UserController implements IBaseController {
}
@LogHttpCaller
@PasswordFineteValidator
@PasswordErrorFiniteValidator
@RequestMapping("/login")
public JsonResult login(
@RequestParam(required = false, defaultValue = "1") Long channelId, String appChannel,
......@@ -363,7 +320,7 @@ public class UserController implements IBaseController {
@RequestParam String verificationCode, @RequestParam(required = false) Long channelId,
@RequestParam(required = false) Long registerFrom,
@RequestParam(required = false)Long btRegisterChannelId,@RequestParam(required = false)String dimension) {
LOGGER.info("用户注册, phoneNo:{}, verificationCode:{}, channelId:{}, registerFrom:{},btRegisterChannelId:{},dimension:{}", phoneNo, verificationCode, channelId, registerFrom,btRegisterChannelId,dimension);
LOGGER.info("[/user/register]用户注册, phoneNo:{}, verificationCode:{}, channelId:{}, registerFrom:{},btRegisterChannelId:{},dimension:{}", phoneNo, verificationCode, channelId, registerFrom,btRegisterChannelId,dimension);
if (!ValidationUtil.validatePhoneNo(phoneNo)) {
LOGGER.info("用户注册失败,手机号错误, registerFrom:{}, phoneNo:{}", registerFrom, phoneNo);
return JsonResult.buildErrorStateResult("手机号错误", null);
......@@ -532,7 +489,7 @@ public class UserController implements IBaseController {
//增加登陆统计发送
UserStatistics statistics=new UserStatistics(user,dimension,1,channelId);
MqUtils.sendLoanVest(statistics);
LOGGER.info("用户登陆成功phonNo:{},当前ip:{},用户登陆信息:{}",user.getPhoneNo(), IPUtil.getRemoteIP(request),statistics);
LOGGER.info("用户登陆成功phonNo:{},当前ip:{},用户登陆信息:{}",user.getPhoneNo(), IPUtil.getRemoteIP(request), statistics);
return new JsonResult(sessionService.createSession(channelId, createdFrom, appChannel, user, merchant));
}
......@@ -549,110 +506,38 @@ public class UserController implements IBaseController {
} catch (UnsupportedEncodingException e) {
LOGGER.error("不支持的编码: ", e);
}
String clientIp = getIp();
String[] credentialArr = bufStr.split(":");
if (credentialArr.length != 2) {
LOGGER.info("用户登录失败:{}", bufStr);
// 向该ipv4添加错误计数器
countErrorByIpv4();
lockIpv4Service.countErrorByIpv4(clientIp);
return null;
}
LOGGER.info("用户正在登录... [{}]", credentialArr);
String phoneNo = credentialArr[0];
String pass = credentialArr[1];
LOGGER.info("用户正在登录... [{}]", phoneNo);
User user = userService.findByPhoneWithCache(phoneNo);
if (user == null || !user.getEnable()) {
// 向该phoneNo添加错误计数器
countErrorByPhoneNo(phoneNo);
lockIpv4Service.countErrorByPhoneNo(phoneNo);
// 向该ipv4添加错误计数器
countErrorByIpv4();
lockIpv4Service.countErrorByIpv4(clientIp);
return null;
}
//验证密码
if (!validatePassword(pass, user.getPassword())) {
// 向该phoneNo添加错误计数器
countErrorByPhoneNo(phoneNo);
lockIpv4Service.countErrorByPhoneNo(phoneNo);
// 向该ipv4添加错误计数器
countErrorByIpv4();
lockIpv4Service.countErrorByIpv4(clientIp);
return null;
}
// 向该ipv4添加成功计数器
lockIpv4Service.countSuccessByIpv4(clientIp);
return user;
}
/**
* 向该phoneNo添加错误计数器
* @param phoneNo
*/
private void countErrorByPhoneNo(String phoneNo) {
// 密码错误时,给该账号添加计数器
String key = Constants.REDIS_PASSWORD_ERROR_COUNT + phoneNo;
if (!stringRedisTemplate.hasKey(key)) {
LOGGER.info("添加错误计数器,key={}", key);
stringRedisTemplate.opsForValue().set(key, String.valueOf(0), DateUtils.getSeconds(), TimeUnit.SECONDS);
}
// 密码错误计数
Long errorCount = stringRedisTemplate.opsForValue().increment(key, 1L);
if (errorCount > Constants.Image_Need_Count) {
throw new PasswordErrorLimitException("用户名或密码不正确");
} else if (Objects.equals(errorCount, Constants.Image_Need_Count)) {
throw new PasswordErrorLimitException("请输入图形验证码");
}
}
/**
* 向该ipv4添加错误计数器
*/
private void countErrorByIpv4() {
// Todo -- 全天候开放监控
/*if(!ValidationUtil.isAtDangerousTime()){
return;
}*/
String ipv4 = getIp();
if (StringUtils.isNotBlank(ipv4) && !ValidationUtil.validateLocalIpv4(ipv4)) {
String ipv4Key = getIpKey(ipv4);
if(!stringRedisTemplate.hasKey(ipv4Key)){
// 计数周期1分钟
stringRedisTemplate.opsForValue().set(ipv4Key, String.valueOf(0), Constants.IPV4_COUNT_MINUTES, TimeUnit.MINUTES);
}
Long count = stringRedisTemplate.opsForValue().increment(ipv4Key, 1L);
LOGGER.info("Lock_ipv4: count ip access: ip={}, count={}", ipv4, count);
lockIpv4(ipv4, count);
}
}
/**
* 锁定IPV4
* @param ip - 目标ip
* @param count - 错误计数
*/
private void lockIpv4(String ip, long count){
// 每分钟计数阈值
long counts = Constants.IPV4_LOCK_ON_COUNTS;
String redisCounts = stringRedisTemplate.opsForValue().get(Constants.IPV4_LOCK_ON_COUNTS_REDIS);
if(StringUtils.isNumeric(redisCounts) && Integer.valueOf(redisCounts) > 0){
counts = Integer.valueOf(redisCounts);
}
if(count < counts){
return;
}
// 锁定时长
long minutes = Constants.IPV4_LOCK_MINUTES;
String redisMinutes = stringRedisTemplate.opsForValue().get(Constants.IPV4_LOCK_ON_COUNTS_REDIS);
if(StringUtils.isNumeric(redisMinutes) && Integer.valueOf(redisMinutes) > 0){
minutes = Integer.valueOf(redisMinutes);
}
String lockIpv4Key = getLockIpv4Key(ip);
stringRedisTemplate.opsForValue().set(lockIpv4Key, Boolean.TRUE.toString(), minutes, TimeUnit.MINUTES);
LOGGER.info("Lock_ipv4: locked ip access:{}, error overstep {} times in {} minutes, do lock {} minutes", ip, counts, Constants.IPV4_COUNT_MINUTES, minutes);
}
private final static String getIpKey(String ipv4){
return Constants.REDIS_PASSWORD_ERROR_COUNT_FOR_IPV4 + ipv4;
}
private final static String getLockIpv4Key(String ipv4){
return Constants.IPV4_LOCK + ipv4;
}
private boolean validatePassword(String paramPass, String targetPassword) {
return StringUtils.defaultString(targetPassword, "").equals(PasswordUtil.MD5(paramPass.toLowerCase() + pwdSalt));
}
......
......@@ -34,7 +34,9 @@ public class IPWhiteListInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(isDebug !=0) return true;
if(isDebug !=0){
return true;
}
String remoteIP = IPUtil.getRemoteIP(request);
if(StringUtils.isNoneBlank(remoteIP)){
remoteIP = remoteIP.trim();
......
package cn.quantgroup.xyqb.service.user;
/**
* IPV4锁机制Service
* @author renwc
*/
public interface ILockIpv4Service {
/**
* 向该phoneNo添加错误计数器
* @param phoneNo
*/
void countErrorByPhoneNo(String phoneNo);
/**
* 向该ipv4添加错误计数器
* @param ip - 目标ip
*/
void countErrorByIpv4(String ip);
/**
* 锁定IPV4
* @param ip - 目标ip
* @param count - 错误计数
*/
void lockErrorIpv4(String ip, long count);
/**
* 向该phoneNo添加错误计数器
* @param ip - 目标ip
*/
void countSuccessByIpv4(String ip);
/**
* 锁定IPV4
* @param ip - 目标ip
* @param count - 错误计数
*/
void lockSuccessIpv4(String ip, long count);
}
package cn.quantgroup.xyqb.service.user.impl;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.exception.PasswordErrorLimitException;
import cn.quantgroup.xyqb.service.user.ILockIpv4Service;
import cn.quantgroup.xyqb.util.DateUtils;
import cn.quantgroup.xyqb.util.IPUtil;
import cn.quantgroup.xyqb.util.ValidationUtil;
import org.apache.commons.lang.StringUtils;
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.Service;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* IPV4锁机制Service实现
* @author renwc
*/
@Service
public class LockIpv4ServiceImpl implements ILockIpv4Service {
private static final Logger LOGGER = LoggerFactory.getLogger(ILockIpv4Service.class);
@Autowired
@Qualifier("stringRedisTemplate")
private RedisTemplate<String, String> stringRedisTemplate;
@Override
public void countErrorByPhoneNo(String phoneNo) {
// 密码错误时,给该账号添加计数器
String key = Constants.REDIS_PASSWORD_ERROR_COUNT + phoneNo;
if (!stringRedisTemplate.hasKey(key)) {
LOGGER.info("添加错误计数器,key={}", key);
stringRedisTemplate.opsForValue().set(key, String.valueOf(0), DateUtils.getSeconds(), TimeUnit.SECONDS);
}
// 密码错误计数
Long errorCount = stringRedisTemplate.opsForValue().increment(key, 1L);
if (errorCount > Constants.Image_Need_Count) {
LOGGER.info("用户名或密码不正确,phoneNo={}", phoneNo);
throw new PasswordErrorLimitException("用户名或密码不正确");
} else if (Objects.equals(errorCount, Constants.Image_Need_Count)) {
LOGGER.info("请输入图形验证码,phoneNo={}", phoneNo);
throw new PasswordErrorLimitException("请输入图形验证码");
}
}
/**
* 对白名单之外的所有公有、私有IP执行错误计数
* @param ipv4
*/
@Override
public void countErrorByIpv4(String ipv4) {
// Todo -- 全天候开放监控
/*if(!ValidationUtil.isAtDangerousTime()){
return;
}*/
if (ValidationUtil.validateIpv4(ipv4) && !IPUtil.whiteOf(ipv4)) {
String ipv4Key = getErrorIpKey(ipv4);
if(!stringRedisTemplate.hasKey(ipv4Key)){
// 计数周期1分钟
stringRedisTemplate.opsForValue().set(ipv4Key, String.valueOf(0), Constants.IPV4_FAILED_COUNT_MINUTES, TimeUnit.MINUTES);
}
Long count = stringRedisTemplate.opsForValue().increment(ipv4Key, 1L);
LOGGER.info("Lock_ipv4: count error ip access: ip={}, count={}", ipv4, count);
lockErrorIpv4(ipv4, count);
}
}
@Override
public void lockErrorIpv4(String ip, long count){
// 每分钟计数阈值
long counts = Constants.IPV4_LOCK_ON_FAILED_COUNTS;
String redisCounts = stringRedisTemplate.opsForValue().get(Constants.IPV4_LOCK_ON_COUNTS_REDIS);
if(StringUtils.isNumeric(redisCounts) && Integer.valueOf(redisCounts) > 0){
counts = Integer.valueOf(redisCounts);
}
if(count < counts){
return;
}
// 锁定时长
long minutes = Constants.IPV4_FAILED_LOCK_MINUTES;
String redisMinutes = stringRedisTemplate.opsForValue().get(Constants.IPV4_LOCK_ON_COUNTS_REDIS);
if(StringUtils.isNumeric(redisMinutes) && Integer.valueOf(redisMinutes) > 0){
minutes = Integer.valueOf(redisMinutes);
}
String lockIpv4Key = getLockIpv4Key(ip);
stringRedisTemplate.opsForValue().set(lockIpv4Key, Boolean.TRUE.toString(), minutes, TimeUnit.MINUTES);
LOGGER.info("Lock_ipv4: locked error ip access:{}, error overstep {} times in {} minutes, do lock {} minutes", ip, counts, Constants.IPV4_FAILED_COUNT_MINUTES, minutes);
}
/**
* 对白名单之外的所有公有、私有IP执行成功计数
* @param ipv4
*/
@Override
public void countSuccessByIpv4(String ipv4) {
// Todo -- 全天候开放监控
/*if(!ValidationUtil.isAtDangerousTime()){
return;
}*/
if (ValidationUtil.validateIpv4(ipv4) && !IPUtil.whiteOf(ipv4)) {
String ipv4Key = getSuccessIpKey(ipv4);
if(!stringRedisTemplate.hasKey(ipv4Key)){
// 计数周期1分钟
stringRedisTemplate.opsForValue().set(ipv4Key, String.valueOf(0), Constants.IPV4_SUCCESS_COUNT_MINUTES, TimeUnit.MINUTES);
}
Long count = stringRedisTemplate.opsForValue().increment(ipv4Key, 1L);
LOGGER.info("Lock_ipv4: count success ip access: ip={}, count={}", ipv4, count);
lockSuccessIpv4(ipv4, count);
}
}
@Override
public void lockSuccessIpv4(String ip, long count){
// 每小时计数阈值
if(count < Constants.IPV4_LOCK_ON_SUCCESS_COUNTS){
return;
}
// 锁定时长
String lockIpv4Key = getLockIpv4Key(ip);
stringRedisTemplate.opsForValue().set(lockIpv4Key, Boolean.TRUE.toString(), Constants.IPV4_SUCCESS_LOCK_MINUTES, TimeUnit.MINUTES);
LOGGER.info("Lock_ipv4: locked success ip access:{}, success overstep {} times in {} minutes, do lock {} minutes", ip, Constants.IPV4_LOCK_ON_SUCCESS_COUNTS, Constants.IPV4_SUCCESS_COUNT_MINUTES, Constants.IPV4_SUCCESS_LOCK_MINUTES);
}
private final static String getErrorIpKey(String ipv4){
return Constants.REDIS_PASSWORD_ERROR_COUNT_FOR_IPV4 + ipv4;
}
private final static String getSuccessIpKey(String ipv4){
return Constants.REDIS_PASSWORD_SUCCESS_COUNT_FOR_IPV4 + ipv4;
}
private final static String getLockIpv4Key(String ipv4){
return Constants.IPV4_LOCK + ipv4;
}
}
package cn.quantgroup.xyqb.util;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
/**
* IP地址工具类
*
* @author mengfan.feng
* @time 2015-10-27 11:41
* @author renwc
* @date 2017-12-01
*/
public class IPUtil {
/**
* IP白名单
*
* 192.168.3.0/24 - 公有云测试业务
* 192.168.4.0/24 - 公有云测试业务
* 172.16.0.0/16 - 公有云正式业务
* 172.20.0.0/16 - 3B私有云
* 172.30.0.0/16 - 3C私有云
*/
private static final Set<String> whiteAddr = Sets.newHashSet();
static {
String[] ips = {"172.16.", "172.20.", "172.30.", "192.168.3.", "192.168.4."};
whiteAddr.addAll(Arrays.asList(ips));
}
/**
* 判断是否为白名单IP
* @param ipv4
* @return
*/
public static final boolean whiteOf(String ipv4){
if(ValidationUtil.validateIpv4(ipv4)){
for(String ipField : whiteAddr){
if(ipv4.startsWith(ipField)){
return true;
}
}
}
return false;
}
/**
* 通过指定请求获得对应的远程ip地址
* 打印IP
* @param logger - 日志记录器
* @param request - 真实HttpServletRequest请求
*/
public static final void logIp(Logger logger, HttpServletRequest request) {
if(Objects.isNull(request)){
return;
}
String remoteAddr = request.getRemoteAddr();
String xRealIp = IPUtil.getRemoteIP(request);
String xOriginalClientIp = request.getHeader("x-original-client-ip");
logger.info("Test ips:[client={}, old={}, new={}]", remoteAddr, xRealIp, xOriginalClientIp);
}
/**
* 获取请求客户端的真实ip地址
*
* @param request
* @return
*/
public static String getRemoteIP(HttpServletRequest request) {
String ip = request.getHeader("x-real-ip");
String ip = request.getHeader("x-original-client-ip");
if (ValidationUtil.validateIpv4(ip) && !ip.startsWith("127.")) {
return ip;
}
// Todo - 以下为老代码,保留容错
ip = request.getHeader("x-real-ip");
if (StringUtils.isEmpty(ip)) {
ip = request.getRemoteAddr();
}
//过滤反向代理的ip
String[] stemps = ip.split(",");
if (stemps != null && stemps.length >= 1) {
//得到第一个IP,即客户端真实IP
ip = stemps[0];
}
ip = ip.trim();
if (ip.length() > 23) {
ip = ip.substring(0, 23);
}
return ip;
}
}
......@@ -18,7 +18,7 @@ public class ValidationUtil {
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 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 String localIpv4RegExp = "^((172\\.(1[0-6]|2[0-9]|3[01]))|(192\\.168|169\\.254)|((127|10)\\.(2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?)))(\\.(2[0-4][0-9]|25[0-5]|[01]?[0-9][0-9]?)){2}$";
private static Pattern phonePattern = Pattern.compile(phoneRegExp);
private static Pattern chinesePattern = Pattern.compile(chineseNameRegExp);
......
......@@ -111,7 +111,6 @@ public class LoginForH5Tests {
// 第一次使用TokenOnce
String aspectUri = "/user/login/fastForH5";
mvcResult = mvc.perform(MockMvcRequestBuilders.get(aspectUri).accept(MediaType.APPLICATION_JSON)
.header(Constants.ONE_TIME_TOKEN, oneTimeToken)
.header("authorization", authorization))
.andExpect(status().isOk())
.andReturn();
......@@ -123,7 +122,6 @@ public class LoginForH5Tests {
Assert.assertNotEquals("0002", businessCode);
// 使用过期的TokenOnce与verificationCode
mvcResult = mvc.perform(MockMvcRequestBuilders.get(aspectUri).accept(MediaType.APPLICATION_JSON)
.header(Constants.ONE_TIME_TOKEN, oneTimeToken)
.header("authorization", authorization))
.andExpect(status().isOk())
.andReturn();
......
......@@ -98,7 +98,6 @@ public class OneTimeTokenTests {
// 第一次使用OneTime-Token
String aspectUri = "/user/loginForH5";
mvcResult = mvc.perform(MockMvcRequestBuilders.get(aspectUri).accept(MediaType.APPLICATION_JSON)
.param(Constants.ONE_TIME_TOKEN, oneTimeToken)
.param("phoneNo", userName)
.param("password", password))
.andExpect(status().isOk())
......@@ -111,7 +110,6 @@ public class OneTimeTokenTests {
Assert.assertNotEquals("0002", businessCode);
// 使用过期的TokenOnce
mvcResult = mvc.perform(MockMvcRequestBuilders.get(aspectUri).accept(MediaType.APPLICATION_JSON)
.param(Constants.ONE_TIME_TOKEN, oneTimeToken)
.param("phoneNo", userName)
.param("password", password))
.andExpect(status().isOk())
......
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