Commit 91bb84b7 authored by 董建华's avatar 董建华

增加拒绝策略

parent e6e832e1
package cn.quantgroup.xyqb.aspect.captcha;
/**
* 设备拦截器
*/
public @interface DeviceInterceptor {
}
package cn.quantgroup.xyqb.aspect.captcha;
import cn.quantgroup.xyqb.entity.CountDevice;
import cn.quantgroup.xyqb.entity.LoginInfo;
import cn.quantgroup.xyqb.entity.enums.Device;
import cn.quantgroup.xyqb.entity.enums.KeyType;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.repository.LoginInfoRepository;
import cn.quantgroup.xyqb.repository.WhiteListRepository;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* @author :dongjianhua
* @date :Created in 2020/11/20 14:55
* @description:设备拦截器切面
* @modified By:
* @version:
*/
@Aspect
@Component
@Slf4j
@Order(10)//比较靠后执行
public class DeviceInterceptorAspect {
@Resource
private LoginInfoRepository loginInfoRepository;
@Resource
private WhiteListRepository whiteListRepository;
private static final String DEFAULT_CODE = "00000000-0000-0000-0000-000000000000";
/**
* 设备拒绝阈值
*/
private static final Long DEVICE_REFUSE_COUNT = 3L;
/**
* 警示语
*/
private final String ALERT_WORDS = "检测到您的设备上账号登录异常,已被强制退出并暂时冻结您的账号。联系客服400-002-0061";
/**
* 拒绝策略e
*/
private static Map<Device, Long> DEVICE_REFUSE_STRATEGY = Maps.newHashMap();
static {
DEVICE_REFUSE_STRATEGY.put(Device.ANDROID, 3L);
DEVICE_REFUSE_STRATEGY.put(Device.IOS, 7L);
}
@Pointcut("@annotation(cn.quantgroup.xyqb.aspect.captcha.DeviceInterceptor)")
public void pointCutAt() {
}
@Around("pointCutAt()")
private Object around(ProceedingJoinPoint pjp) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
/**
* 拿参数
*/
String idfa = request.getHeader("idfa");
String scDeviceId = request.getHeader("scDeviceId");
String realIp = request.getHeader("x-real-ip");
String deviceCode = request.getHeader("x-user-platform");
String phone = request.getHeader("x-user-phone");
String deviceId = null;
if (null != scDeviceId && !"".equals(scDeviceId)) {
deviceId = scDeviceId;
} else if (null != idfa && !"".equals(idfa)) {
deviceId = idfa;
} else {
/**
* 没有获取到就用默认的
*/
deviceId = DEFAULT_CODE;
}
if (!verification(scDeviceId, phone)) {
return JsonResult.buildErrorStateResult(ALERT_WORDS, null);
}
/**
* 保存登录信息
*/
saveLoginInfo(phone, deviceCode, deviceId, realIp);
try {
return pjp.proceed();
} catch (Exception e) {
throw e;
}
}
/**
* @param deviceId
* @param phone
* @return
*/
private boolean verification(String deviceId, String phone) {
try {
/**
* 默认code不需要
*/
if (DEFAULT_CODE.equals(deviceId)) {
return true;
}
/**
* 如果没传手机也放过吧
*/
if (null == phone || "".equals(phone)) {
log.warn("怎么没传手机号进来啊暂时放过吧");
return true;
}
Long deviceNum = loginInfoRepository.countByDeviceId(deviceId);
if (DEVICE_REFUSE_COUNT.compareTo(deviceNum) < 0) {
if (!isWhite(deviceId, KeyType.DEVICEID)) {
log.warn("此设备登录命中拒绝策略,并且未在白名单deviceId:{}超过{}个拒绝登录", deviceId, deviceNum);
return true;
}
log.warn("此设备登录命中拒绝策略存在白名单deviceId:{}", deviceId);
return false;
}
List<CountDevice> countDevices = loginInfoRepository.countByPhoneAndDevice(phone);
if (CollectionUtils.isEmpty(countDevices)) {
return true;
}
for (CountDevice countDevice : countDevices) {
Long aLong = DEVICE_REFUSE_STRATEGY.get(countDevice.getDevice());
if (null != aLong && countDevice.getNum().compareTo(aLong) > 0) {
if (!isWhite(deviceId, KeyType.PHONE)) {
log.warn("此账户登录命中拒绝策略并且没有白名单phone:{},device", phone);
return false;
}
log.warn("此账户登录命中拒绝策略存在白名单phone:{}", phone);
}
}
return true;
} catch (Exception e) {
log.error("用户登录策略校验异常了", e);
}
return true;
}
/**
* 保存信息
*
* @param phone
* @param deviceCode
* @param deviceId
* @param ip
*/
private void saveLoginInfo(String phone, String deviceCode, String deviceId, String ip) {
/**
* 默认code不需要
* 默认CODE 不存
*/
if (DEFAULT_CODE.equals(deviceId)) {
return;
}
Device device = Device.valueOfCode(deviceCode);
if (null == device) {
log.warn("咋回事啊没找到code对应的设备code:{}", deviceCode);
}
Long count = 1L;
LoginInfo info = loginInfoRepository.getFirstByPhoneNoAndDevice(phone, device);
if (null == info) {
info = new LoginInfo();
} else {
count = info.getLoginCount() + 1L;
}
info.setPhoneNo(phone);
info.setDeviceId(deviceId);
info.setLastLoginAt(Timestamp.valueOf(LocalDateTime.now()));//当前时间
info.setDevice(device);
info.setLoginCount(count);
info.setLastIp(ip);
loginInfoRepository.save(info);
}
/**
* 是否在白名单
*
* @param key
* @param type
* @return
*/
private boolean isWhite(String key, KeyType type) {
return whiteListRepository.countByKeyEqualsAndTypeEquals(key, type) > 0;
}
/**
* 【登录设备反欺诈策略】
*
* 一、拿设备唯一码
* 1.安卓: 只拿神策$device_id 缺失不做处理
* 2.IOS: 先拿神策$device_id 缺失则再拿IDFA 最后还是缺失 和 00000000-0000-0000-0000-000000000000 不做处理
*
* 二、设备维度策略
* 1.触发条件: 当该设备在90天内 累计尝试登录超过3个uuid账号
* 2.设备措施: 该设备冻结加入黑名单 该设备上任何uuid账号都不能登录了 如有客诉核实为误杀后再从黑名单中剔除 加入白名单; (预计涉及万3的安卓设备,万5的IOS设备;包含了本次山东赵某的设备)
*
* 三、账号维度策略
* 1.触发条件: 当该uuid账号在90天内 在超过(安卓:3,IOS:7)个设备尝试过登录时 (目前同一IOS的设备唯一码可能会变,重复率约是安卓的1倍多)
* 2.账号措施:该uuid帐号冻结加入黑名单 该uuid帐号不能在任何设备登录了 如有客诉核实为误杀后再从黑名单中剔除 加入白名单 (预计涉及万6的安卓用户,万5的IOS用户)
*/
}
package cn.quantgroup.xyqb.entity;
/**
* @author :dongjianhua
* @date :Created in 2020/11/23 9:46
* @description:
* @modified By:
* @version:
*/
import cn.quantgroup.xyqb.entity.enums.Device;
import lombok.Data;
import lombok.ToString;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.NamedNativeQueries;
import javax.persistence.NamedNativeQuery;
/**
* 查询营销短信统计信息
*
* @author renwc
* @date 2020-07-14
*/
@NamedNativeQueries(
{
@NamedNativeQuery(
name = "LoginInfo.countByPhoneAndDevice",
query = "select count(1) num ,device from login_info where phone_no=?1 and last_login_at> DATE_ADD(CURRENT_TIMESTAMP(),INTERVAL -90 DAY group by device",
resultClass = CountDevice.class
)
}
)
@Data
@ToString
@Entity
public class CountDevice {
@Column(name = "device")
private Device device;
@Column(name = "num")
private Long num;
}
package cn.quantgroup.xyqb.entity;
import cn.quantgroup.xyqb.entity.enums.Device;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.sql.Timestamp;
/**
* @author :dongjianhua
* @date :Created in 2020/11/23 8:26
* @description:用户登录信息表
* @modified By:
* @version: 1.0
*/
@Data
@Entity
@Table(name = "login_info")
public class LoginInfo extends BaseEntity {
@Column(name = "phone_no")
private String phoneNo;
@Column(name = "device_id")
private String deviceId;
@Column(name = "device")
private Device device;
@Column(name = "last_ip")
private String lastIp;
@Column(name = "login_count")
private Long loginCount;
@Column(name = "last_login_at")
private Timestamp lastLoginAt;
/**
* CREATE TABLE `login_info` (
* `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
* `phone_no` varchar(15) NOT NULL COMMENT '手机号',
* `device_id` varchar(50) NOT NULL COMMENT '设备ID',
* `device` int(2) DEFAULT NULL COMMENT '设备类型(0安卓,1IOS)',
* `last_ip` varchar(30) DEFAULT NULL COMMENT '同手机设备最后一次登录的IP',
* `login_count` int(11) DEFAULT '1' COMMENT '同账户设备登录次数',
* `last_login_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最后一次登陆时间',
* `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间(第一次登陆时间)',
* `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
* PRIMARY KEY (`id`),
* UNIQUE KEY `phone_deviceid_idx` (`phone_no`,`device_id`) USING BTREE,
* KEY `deviceid_idx` (`device_id`) USING BTREE,
* KEY `create_at_idx` (`created_at`) USING BTREE,
* KEY `last_login_at_idx` (`last_login_at`) USING BTREE
* ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='登录信息表';
*/
}
package cn.quantgroup.xyqb.entity;
import cn.quantgroup.xyqb.entity.enums.KeyType;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
/**
* @author :dongjianhua
* @date :Created in 2020/11/23 11:30
* @description:白名单
* @modified By:
* @version:
*/
@Entity
@Table(name = "white_list")
@Data
public class WhiteList extends BaseEntity {
@Column(name = "key")
private String key;
@Column(name = "key_type")
private KeyType type;
@Column(name = "enable")
private Boolean enable;
}
package cn.quantgroup.xyqb.entity.enums;
import lombok.Getter;
@Getter
public enum Device {
ANDROID("安卓", "android"),
IOS("IOS", "ios");
private String desc;
private String code;
Device(String desc, String code) {
this.desc = desc;
this.code = code;
}
public static Device valueOfCode(String code) {
if(null == code){
return null;
}
for (Device device : Device.values()) {
if (device.code.equals(code)) {
return device;
}
}
return null;
}
}
package cn.quantgroup.xyqb.entity.enums;
public enum KeyType {
PHONE,//手机
DEVICEID,//设备ID
IP;//IP
}
package cn.quantgroup.xyqb.repository;
import cn.quantgroup.xyqb.entity.CountDevice;
import cn.quantgroup.xyqb.entity.LoginInfo;
import cn.quantgroup.xyqb.entity.enums.Device;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.scheduling.annotation.Async;
import java.util.List;
public interface LoginInfoRepository extends JpaRepository<LoginInfo, Long> {
/**
* 根据设备ID看有多少手机号
*
* @param deviceId 设备维度策略
* @return
*/
@Query(value = "select count(1) from login_info where device_id=?1 and last_login_at> DATE_ADD(CURRENT_TIMESTAMP(),INTERVAL -90 DAY)", nativeQuery = true)
Long countByDeviceId(String deviceId);
/**
* 账号维度策略
*
* @param phone
* @return
*/
// @Query(value = "select count(1) ,device from login_info where phone_no=?1 and last_login_at> DATE_ADD(CURRENT_TIMESTAMP(),INTERVAL -90 DAY group by device", nativeQuery = true)
List<CountDevice> countByPhoneAndDevice(String phone);
/**
* 查询有没有(手机+设备唯一)
* @param phone
* @param device
* @return
*/
LoginInfo getFirstByPhoneNoAndDevice(String phone, Device device);
@Async
@Override
<S extends LoginInfo> S save(S s);
}
package cn.quantgroup.xyqb.repository;
import cn.quantgroup.xyqb.entity.WhiteList;
import cn.quantgroup.xyqb.entity.enums.KeyType;
import org.springframework.data.jpa.repository.JpaRepository;
public interface WhiteListRepository extends JpaRepository<WhiteList, Long> {
Long countByKeyEqualsAndTypeEquals(String key, KeyType type);
}
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