Commit e5c735f7 authored by 张宏至's avatar 张宏至

add:去掉反欺诈登陆验证

parent 8ace2dbe
package cn.quantgroup.xyqb.aspect.captcha; package cn.quantgroup.xyqb.aspect.captcha;
import cn.qg.ec.model.base.BusinessEventBaseInfo; import cn.qg.ec.model.base.BusinessEventBaseInfo;
import cn.qg.ec.model.user.UserLoginRegEvent; import cn.qg.ec.model.user.UserLoginRegEvent;
import cn.quantgroup.xyqb.entity.User; import cn.quantgroup.xyqb.entity.User;
import cn.quantgroup.xyqb.model.AuthBean; import cn.quantgroup.xyqb.model.AuthBean;
import cn.quantgroup.xyqb.risk.entity.CountDevice; import cn.quantgroup.xyqb.risk.entity.CountDevice;
import cn.quantgroup.xyqb.entity.enums.Device; import cn.quantgroup.xyqb.entity.enums.Device;
import cn.quantgroup.xyqb.entity.enums.KeyType; import cn.quantgroup.xyqb.entity.enums.KeyType;
import cn.quantgroup.xyqb.model.JsonResult; import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.model.LoginRefuseResult; import cn.quantgroup.xyqb.model.LoginRefuseResult;
import cn.quantgroup.xyqb.risk.entity.LoginInfo; import cn.quantgroup.xyqb.risk.entity.LoginInfo;
import cn.quantgroup.xyqb.risk.repository.LoginInfoRepository; import cn.quantgroup.xyqb.risk.repository.LoginInfoRepository;
import cn.quantgroup.xyqb.risk.repository.WhiteListRepository; import cn.quantgroup.xyqb.risk.repository.WhiteListRepository;
import cn.quantgroup.xyqb.risk.uereventcollecting.UserEventCollectingUtil; import cn.quantgroup.xyqb.risk.uereventcollecting.UserEventCollectingUtil;
import cn.quantgroup.xyqb.service.user.IUserService; import cn.quantgroup.xyqb.service.user.IUserService;
import cn.quantgroup.xyqb.service.user.impl.UserServiceImpl; import cn.quantgroup.xyqb.service.user.impl.UserServiceImpl;
import cn.quantgroup.xyqb.util.encrypt.Md5Util; import cn.quantgroup.xyqb.util.encrypt.Md5Util;
import com.amazonaws.util.Md5Utils; import com.amazonaws.util.Md5Utils;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.qiniu.util.Md5; import com.qiniu.util.Md5;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.util.Map; import java.util.Map;
/** /**
* @author :dongjianhua * @author :dongjianhua
* @date :Created in 2020/11/20 14:55 * @date :Created in 2020/11/20 14:55
* @description:设备拦截器切面 * @description:设备拦截器切面
* @modified By: * @modified By:
* @version: * @version:
*/ */
@Aspect @Aspect
@Component @Component
@Slf4j @Slf4j
@Order(10)//同心圆最外层 @Order(10)//同心圆最外层
public class LoginInterceptorAspect { public class LoginInterceptorAspect {
@Resource @Resource
private LoginInfoRepository loginInfoRepository; private LoginInfoRepository loginInfoRepository;
@Resource @Resource
private WhiteListRepository whiteListRepository; private WhiteListRepository whiteListRepository;
@Resource @Resource
private IUserService iUserService; private IUserService iUserService;
private static final String DEFAULT_CODE = "00000000-0000-0000-0000-000000000000"; private static final String DEFAULT_CODE = "00000000-0000-0000-0000-000000000000";
/** /**
* 设备拒绝阈值 * 设备拒绝阈值
*/ */
private static final Long DEVICE_REFUSE_COUNT = 3L; private static final Long DEVICE_REFUSE_COUNT = 3L;
/** /**
* 风控拒绝策略1设备维度 2 账号维度 * 风控拒绝策略1设备维度 2 账号维度
*/ */
private static final int[] RISK_STRATEGY = {1, 2}; private static final int[] RISK_STRATEGY = {1, 2};
/** /**
* 警示语 * 警示语
*/ */
private final String ALERT_WORDS = "检测到您的设备上账号登录异常,已被强制退出并暂时冻结您的账号。联系客服400-002-0061"; private final String ALERT_WORDS = "检测到您的设备上账号登录异常,已被强制退出并暂时冻结您的账号。联系客服400-002-0061";
/** /**
* 拒绝策略e * 拒绝策略e
*/ */
private static Map<Device, Long> DEVICE_REFUSE_STRATEGY = Maps.newHashMap(); private static Map<Device, Long> DEVICE_REFUSE_STRATEGY = Maps.newHashMap();
static { static {
DEVICE_REFUSE_STRATEGY.put(Device.ANDROID, 3L); DEVICE_REFUSE_STRATEGY.put(Device.ANDROID, 3L);
DEVICE_REFUSE_STRATEGY.put(Device.IOS, 7L); DEVICE_REFUSE_STRATEGY.put(Device.IOS, 7L);
} }
@Pointcut("@annotation(cn.quantgroup.xyqb.aspect.captcha.LoginInterceptor)") @Pointcut("@annotation(cn.quantgroup.xyqb.aspect.captcha.LoginInterceptor)")
public void pointCutAt() { public void pointCutAt() {
} }
@Around("pointCutAt()") @Around("pointCutAt()")
private Object around(ProceedingJoinPoint pjp) throws Throwable { private Object around(ProceedingJoinPoint pjp) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
/** /**
* 拿参数 * 拿参数
*/ */
String idfa = request.getHeader("idfa"); String idfa = request.getHeader("idfa");
String scDeviceId = request.getHeader("scDeviceId"); String scDeviceId = request.getHeader("scDeviceId");
String realIp = request.getHeader("x-real-ip"); String realIp = request.getHeader("x-real-ip");
String deviceCode = request.getHeader("x-user-platform"); String deviceCode = request.getHeader("x-user-platform");
String phone = request.getHeader("x-user-phone"); String phone = request.getHeader("x-user-phone");
String deviceId = null; String deviceId = null;
if (null != scDeviceId && !"".equals(scDeviceId)) { if (null != scDeviceId && !"".equals(scDeviceId)) {
deviceId = scDeviceId; deviceId = scDeviceId;
} else if (null != idfa && !"".equals(idfa)) { } else if (null != idfa && !"".equals(idfa)) {
deviceId = idfa; deviceId = idfa;
} else { } else {
/** /**
* 没有获取到就用默认的 * 没有获取到就用默认的
*/ */
deviceId = DEFAULT_CODE; deviceId = DEFAULT_CODE;
} }
log.info("登录前风控策略开始deviceId:{},phone:{}", deviceId, phone); log.info("登录前风控策略开始deviceId:{},phone:{}", deviceId, phone);
Device device = Device.valueOfCode(deviceCode); Device device = Device.valueOfCode(deviceCode);
/** /**
* 尝试登录发一下 * 尝试登录发一下
*/ */
sendEvent(UserLoginRegEvent.SubEventType.user_attempt_login, device, phone, deviceId, realIp, null); sendEvent(UserLoginRegEvent.SubEventType.user_attempt_login, device, phone, deviceId, realIp, null);
LoginRefuseResult result = verification(scDeviceId, phone, device); // LoginRefuseResult result = verification(scDeviceId, phone, device);
LoginRefuseResult result = LoginRefuseResult.builder().isPass(Boolean.TRUE).build();//去掉反欺诈限制
Object loginResult = null; Object loginResult = null;
try { try {
if (!result.isPass()) { if (!result.isPass()) {
log.warn("登录命中风控策略deviceId:{},phone:{},realIp:{},deviceCode:{}", deviceId, phone, realIp, deviceCode); log.warn("登录命中风控策略deviceId:{},phone:{},realIp:{},deviceCode:{}", deviceId, phone, realIp, deviceCode);
return JsonResult.buildErrorStateResult(ALERT_WORDS, null); return JsonResult.buildErrorStateResult(ALERT_WORDS, null);
} }
loginResult = pjp.proceed(); loginResult = pjp.proceed();
} catch (Exception e) { } catch (Exception e) {
throw e; throw e;
} finally { } finally {
/** /**
* 保存登录信息 * 保存登录信息
*/ */
saveLoginInfo(phone, device, deviceId, realIp, result, loginResult); saveLoginInfo(phone, device, deviceId, realIp, result, loginResult);
} }
return loginResult; return loginResult;
} }
/** /**
* @param deviceId * @param deviceId
* @param phone * @param phone
* @return * @return
*/ */
private LoginRefuseResult verification(String deviceId, String phone, Device device) { private LoginRefuseResult verification(String deviceId, String phone, Device device) {
try { try {
/** /**
* 默认code不需要 * 默认code不需要
*/ */
if (DEFAULT_CODE.equals(deviceId)) { if (DEFAULT_CODE.equals(deviceId)) {
return LoginRefuseResult.builder() return LoginRefuseResult.builder()
.isPass(Boolean.TRUE).build(); .isPass(Boolean.TRUE).build();
} }
/** /**
* 如果没传手机也放过吧 * 如果没传手机也放过吧
*/ */
if (null == phone || "".equals(phone)) { if (null == phone || "".equals(phone)) {
log.warn("怎么没传手机号进来啊暂时放过吧"); log.warn("怎么没传手机号进来啊暂时放过吧");
return LoginRefuseResult.builder() return LoginRefuseResult.builder()
.isPass(Boolean.TRUE).build(); .isPass(Boolean.TRUE).build();
} }
/** /**
* 阈值 * 阈值
*/ */
Long threshold = DEVICE_REFUSE_STRATEGY.get(device); Long threshold = DEVICE_REFUSE_STRATEGY.get(device);
if (null == threshold) { if (null == threshold) {
log.warn("非安卓或者IOS设备登录没有策略就放过phone:{},device:{}", phone, device); log.warn("非安卓或者IOS设备登录没有策略就放过phone:{},device:{}", phone, device);
return LoginRefuseResult.builder() return LoginRefuseResult.builder()
.isPass(Boolean.TRUE).build(); .isPass(Boolean.TRUE).build();
} }
Long deviceNum = loginInfoRepository.countByDeviceId(deviceId, phone); Long deviceNum = loginInfoRepository.countByDeviceId(deviceId, phone);
if (DEVICE_REFUSE_COUNT.compareTo(deviceNum) <= 0) { if (DEVICE_REFUSE_COUNT.compareTo(deviceNum) <= 0) {
if (!isWhite(deviceId, KeyType.DEVICEID)) { if (!isWhite(deviceId, KeyType.DEVICEID)) {
log.warn("此设备登录命中拒绝策略deviceId:{}超过{}个拒绝登录", deviceId, deviceNum); log.warn("此设备登录命中拒绝策略deviceId:{}超过{}个拒绝登录", deviceId, deviceNum);
return LoginRefuseResult.builder() return LoginRefuseResult.builder()
.isPass(Boolean.FALSE) .isPass(Boolean.FALSE)
.rule(RISK_STRATEGY[0]) .rule(RISK_STRATEGY[0])
.threshold(DEVICE_REFUSE_COUNT.intValue()) .threshold(DEVICE_REFUSE_COUNT.intValue())
.value(deviceNum.intValue()) .value(deviceNum.intValue())
.build(); .build();
} }
} }
CountDevice countDevice = loginInfoRepository.countByPhoneAndDevice(phone, device.ordinal(), deviceId); CountDevice countDevice = loginInfoRepository.countByPhoneAndDevice(phone, device.ordinal(), deviceId);
if (null == countDevice) { if (null == countDevice) {
return LoginRefuseResult.builder() return LoginRefuseResult.builder()
.isPass(Boolean.TRUE) .isPass(Boolean.TRUE)
.build(); .build();
} }
/** /**
* 实际的数量 * 实际的数量
*/ */
Long val = countDevice.getNum(); Long val = countDevice.getNum();
if (threshold.compareTo(val) <= 0) { if (threshold.compareTo(val) <= 0) {
if (!isWhite(phone, KeyType.PHONE)) { if (!isWhite(phone, KeyType.PHONE)) {
log.warn("此账户登录命中拒绝策略并且没有白名单phone:{},device:{}", phone, device); log.warn("此账户登录命中拒绝策略并且没有白名单phone:{},device:{}", phone, device);
return LoginRefuseResult.builder() return LoginRefuseResult.builder()
.isPass(Boolean.FALSE) .isPass(Boolean.FALSE)
.rule(RISK_STRATEGY[1]) .rule(RISK_STRATEGY[1])
.threshold(threshold.intValue()) .threshold(threshold.intValue())
.value(Long.valueOf(val).intValue()) .value(Long.valueOf(val).intValue())
.build(); .build();
} }
log.warn("此账户登录命中拒绝策略存在白名单phone:{}", phone); log.warn("此账户登录命中拒绝策略存在白名单phone:{}", phone);
} }
} catch (Exception e) { } catch (Exception e) {
log.error("用户登录策略校验异常了", e); log.error("用户登录策略校验异常了", e);
} }
return LoginRefuseResult.builder() return LoginRefuseResult.builder()
.isPass(Boolean.TRUE) .isPass(Boolean.TRUE)
.build(); .build();
} }
/** /**
* 保存信息 * 保存信息
* *
* @param phone * @param phone
* @param device * @param device
* @param deviceId * @param deviceId
* @param ip * @param ip
*/ */
private void saveLoginInfo(String phone, Device device, String deviceId, String ip, LoginRefuseResult refuseResult, Object loginResult) { private void saveLoginInfo(String phone, Device device, String deviceId, String ip, LoginRefuseResult refuseResult, Object loginResult) {
try { try {
/** /**
* 默认code不需要 * 默认code不需要
* 默认CODE 不存 * 默认CODE 不存
*/ */
if (DEFAULT_CODE.equals(deviceId)) { if (DEFAULT_CODE.equals(deviceId)) {
return; return;
} }
if (null == phone || "".equals(phone)) { if (null == phone || "".equals(phone)) {
log.warn("没有手机号先不存了"); log.warn("没有手机号先不存了");
return; return;
} }
LoginInfo info = new LoginInfo(); LoginInfo info = new LoginInfo();
info.setPhoneNo(phone); info.setPhoneNo(phone);
info.setDeviceId(deviceId); info.setDeviceId(deviceId);
info.setDevice(device); info.setDevice(device);
info.setIp(ip); info.setIp(ip);
info.setIsPass(Boolean.TRUE); info.setIsPass(Boolean.TRUE);
if (!refuseResult.isPass()) { if (!refuseResult.isPass()) {
info.setIsPass(Boolean.FALSE); info.setIsPass(Boolean.FALSE);
info.setHitRule(refuseResult.getRule()); info.setHitRule(refuseResult.getRule());
info.setThreshold(refuseResult.getThreshold()); info.setThreshold(refuseResult.getThreshold());
info.setValue(refuseResult.getValue()); info.setValue(refuseResult.getValue());
} }
if (null == loginResult) { if (null == loginResult) {
info.setIsLogin(Boolean.FALSE); info.setIsLogin(Boolean.FALSE);
info.setLoginFailMsg("系统异常"); info.setLoginFailMsg("系统异常");
if (!refuseResult.isPass()) { if (!refuseResult.isPass()) {
info.setLoginFailMsg("命中风控策略"); info.setLoginFailMsg("命中风控策略");
} }
} else { } else {
if (loginResult instanceof JsonResult) { if (loginResult instanceof JsonResult) {
if (((JsonResult) loginResult).isSuccess()) { if (((JsonResult) loginResult).isSuccess()) {
info.setIsLogin(Boolean.TRUE); info.setIsLogin(Boolean.TRUE);
sendEvent(UserLoginRegEvent.SubEventType.user_only_login, device, phone, deviceId, ip, loginResult); sendEvent(UserLoginRegEvent.SubEventType.user_only_login, device, phone, deviceId, ip, loginResult);
} else { } else {
info.setIsLogin(Boolean.FALSE); info.setIsLogin(Boolean.FALSE);
info.setLoginFailMsg(((JsonResult) loginResult) info.setLoginFailMsg(((JsonResult) loginResult)
.getMsg()); .getMsg());
} }
} else { } else {
log.warn("意想不到的情况按理讲不存在loginResult:{}", loginResult); log.warn("意想不到的情况按理讲不存在loginResult:{}", loginResult);
} }
} }
loginInfoRepository.save(info); loginInfoRepository.save(info);
} catch (Exception e) { } catch (Exception e) {
log.error("保存登录信息异常phone:{}", phone, e); log.error("保存登录信息异常phone:{}", phone, e);
} }
} }
/** /**
* 是否在白名单 * 是否在白名单
* *
* @param key * @param key
* @param type * @param type
* @return * @return
*/ */
private boolean isWhite(String key, KeyType type) { private boolean isWhite(String key, KeyType type) {
return whiteListRepository.countByKeyEqualsAndTypeEqualsAndEnableIsTrue(key, type) > 0; return whiteListRepository.countByKeyEqualsAndTypeEqualsAndEnableIsTrue(key, type) > 0;
} }
/** /**
* 发送登录事件 * 发送登录事件
* *
* @param type * @param type
* @param device * @param device
* @param phone * @param phone
* @param deviceId * @param deviceId
* @param ip * @param ip
* @param loginResult * @param loginResult
*/ */
private void sendEvent(UserLoginRegEvent.SubEventType type, Device device, String phone, String deviceId, String ip, Object loginResult) { private void sendEvent(UserLoginRegEvent.SubEventType type, Device device, String phone, String deviceId, String ip, Object loginResult) {
try { try {
if (null == phone) { if (null == phone) {
log.info("手机号没有直接估计是切面用在了不能用的地方直接放过"); log.info("手机号没有直接估计是切面用在了不能用的地方直接放过");
return; return;
} }
log.info("开始发送登录注册消息phone:{}", phone); log.info("开始发送登录注册消息phone:{}", phone);
UserLoginRegEvent.UserLoginRegEventBuilder builder = UserLoginRegEvent.builder(); UserLoginRegEvent.UserLoginRegEventBuilder builder = UserLoginRegEvent.builder();
builder.maskPhoneNo(phone); builder.maskPhoneNo(phone);
builder.hashPhoneNo(Md5Util.build(Md5Util.build(phone))); builder.hashPhoneNo(Md5Util.build(Md5Util.build(phone)));
builder.subEventType(type); builder.subEventType(type);
BusinessEventBaseInfo.BusinessEventBaseInfoBuilder baseInfoBuilder BusinessEventBaseInfo.BusinessEventBaseInfoBuilder baseInfoBuilder
= BusinessEventBaseInfo.builder(); = BusinessEventBaseInfo.builder();
if (null != loginResult) { if (null != loginResult) {
if (loginResult instanceof JsonResult) { if (loginResult instanceof JsonResult) {
JsonResult jsonResult = ((JsonResult) loginResult); JsonResult jsonResult = ((JsonResult) loginResult);
if (jsonResult.isSuccess()) { if (jsonResult.isSuccess()) {
Object data = jsonResult.getData(); Object data = jsonResult.getData();
if (data instanceof AuthBean) { if (data instanceof AuthBean) {
String uuid = ((AuthBean) data).getUuid(); String uuid = ((AuthBean) data).getUuid();
User user = iUserService.findByUuidWithCache(uuid); User user = iUserService.findByUuidWithCache(uuid);
baseInfoBuilder.userUuid(uuid); baseInfoBuilder.userUuid(uuid);
baseInfoBuilder.channel(String.valueOf(user.getRegisteredFrom())); baseInfoBuilder.channel(String.valueOf(user.getRegisteredFrom()));
} else { } else {
return; return;
} }
} else { } else {
return; return;
} }
} }
} }
baseInfoBuilder.deviceId(deviceId); baseInfoBuilder.deviceId(deviceId);
baseInfoBuilder.ip(ip); baseInfoBuilder.ip(ip);
baseInfoBuilder.deviceType(device.getCode()); baseInfoBuilder.deviceType(device.getCode());
builder.businessEventBaseInfo(baseInfoBuilder.build()); builder.businessEventBaseInfo(baseInfoBuilder.build());
UserEventCollectingUtil.addEvent(builder.build()); UserEventCollectingUtil.addEvent(builder.build());
} catch (Exception e) { } catch (Exception e) {
log.error("发送用户登录事件异常phone:{}", phone); log.error("发送用户登录事件异常phone:{}", phone);
} }
} }
/** /**
* 【登录设备反欺诈策略】 * 【登录设备反欺诈策略】
* *
* 一、拿设备唯一码 * 一、拿设备唯一码
* 1.安卓: 只拿神策$device_id 缺失不做处理 * 1.安卓: 只拿神策$device_id 缺失不做处理
* 2.IOS: 先拿神策$device_id 缺失则再拿IDFA 最后还是缺失 和 00000000-0000-0000-0000-000000000000 不做处理 * 2.IOS: 先拿神策$device_id 缺失则再拿IDFA 最后还是缺失 和 00000000-0000-0000-0000-000000000000 不做处理
* *
* 二、设备维度策略 * 二、设备维度策略
* 1.触发条件: 当该设备在90天内 累计尝试登录超过3个uuid账号 * 1.触发条件: 当该设备在90天内 累计尝试登录超过3个uuid账号
* 2.设备措施: 该设备冻结加入黑名单 该设备上任何uuid账号都不能登录了 如有客诉核实为误杀后再从黑名单中剔除 加入白名单; (预计涉及万3的安卓设备,万5的IOS设备;包含了本次山东赵某的设备) * 2.设备措施: 该设备冻结加入黑名单 该设备上任何uuid账号都不能登录了 如有客诉核实为误杀后再从黑名单中剔除 加入白名单; (预计涉及万3的安卓设备,万5的IOS设备;包含了本次山东赵某的设备)
* *
* 三、账号维度策略 * 三、账号维度策略
* 1.触发条件: 当该uuid账号在90天内 在超过(安卓:3,IOS:7)个设备尝试过登录时 (目前同一IOS的设备唯一码可能会变,重复率约是安卓的1倍多) * 1.触发条件: 当该uuid账号在90天内 在超过(安卓:3,IOS:7)个设备尝试过登录时 (目前同一IOS的设备唯一码可能会变,重复率约是安卓的1倍多)
* 2.账号措施:该uuid帐号冻结加入黑名单 该uuid帐号不能在任何设备登录了 如有客诉核实为误杀后再从黑名单中剔除 加入白名单 (预计涉及万6的安卓用户,万5的IOS用户) * 2.账号措施:该uuid帐号冻结加入黑名单 该uuid帐号不能在任何设备登录了 如有客诉核实为误杀后再从黑名单中剔除 加入白名单 (预计涉及万6的安卓用户,万5的IOS用户)
*/ */
} }
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