检查出⾮⽤户登录,或者账号被盗窃等潜在⻛险挖掘。 通过对用户登录行为进行分析,提高了预测的准确性;可以应用于互联网金融风控技术中,也可应用于普通网站的用户恶意登录识别技术中
参考:https://zhuanli.tianyancha.com/796c88e9c3d67d3a9c59716558818e22
算法实现概述
一种基于用户登录行为分析的风控方法,其特征在于:所述的方法包括用户按键风险识别
、用户登录地风险识别
、密码重试风险识别
、设备来源风险识别
用户登陆习惯风险识别
累计登陆多次风险识别
用户登陆的瞬时位移速度风险识别其中模型;本系统仅仅负责根据用户的登陆数据产生风险评估报告.报告的格式为:
应用信息 | 用户唯一标识 | 登陆地区 | 经纬度 | 登陆序列号 | 评估时间 | 输入特征 | 地区 | 速度 | 设备 | 习惯 | 次数 | 密码 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
zhangsan | Beijing | 116.20,39.56 | UUID | 2020-03.31 10:10:00 | ture | false | true | false | true | false | true |
并不负责对用户的登陆做出定性的分析,仅仅当用户登陆数据过来,由大数据评估系统对用户数据进行评估,然后产生评估报告.由业务系统自行根据评估报告,需用用户采用相关的奖惩措施.介于以上评估报告系统需要用户发送以下数据格式,以辅助系统完成验证:
需要评估登陆数据: 用于本次登陆前产生评估报告的数据.系统拿到评估报告以后,再去做抉择是否升级登陆 该数据需要获取用户的历史登陆成功的数据集合
完成评估.
INFO 2020-03-31 10:12:00 QQ EVALUATE [张三] 6ebaf4ac780f40f486359f3ea6934620 "12355421" Beijing "116.4,39.5" [1200,15000,2100] "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36"
需要成功产生数据: 登陆成功的数据用来作为下一次评估登陆时的历史数据,系统留存最近的一些历史登陆数据集,作为下一次登陆评估的标准
INFO 2020-03-31 10:12:00 QQ EVALUATE [张三] 6ebaf4ac780f40f486359f3ea6934620 "12355421" Beijing "116.4,39.5" [1200,15000,2100] "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36"
Tip:https://regex101.com/ -工具
d:表示匹配数字
():提取的内容
\u4e00-\u9fa5:中文
INFO 2020-03-31 10:12:00 QQ EVALUATE [张三] 6ebaf4ac780f40f486359f3ea6934620 "12355421" Beijing "116.4,39.5" [1200,15000,2100] "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36"
import java.util.regex.Matcher;
import java.util.regex.Pattern;
final String regex = "^INFO\\s\\d{4}-(\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2})\\s([a-z0-9]*)\\s(EVALUATE|SUCCESS)\\s(\\[[a-z0-9\\u4e00-\\u9fa5]*\\])\\s([a-z0-9]{32})\\s(\\\"[a-z0-9]{6,12}\\\")\\s([a-z\\u4e00-\\u9fa5]*)\\s\\\"([0-9\\.\\,]*)\\\"\\s\\[([0-9\\,\\.]*)\\]\\s\\\"(.*)\\\"";
final String string = "INFO 2020-03-31 10:12:00 QQ EVALUATE [张三] 6ebaf4ac780f40f486359f3ea6934620 \"12355421\" Beijing \"116.4,39.5\" [1200,15000,2100] \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36\"";
final Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
final Matcher matcher = pattern.matcher(string);
while (matcher.find()) {
System.out.println("Full match: " + matcher.group(0));
for (int i = 1; i <= matcher.groupCount(); i++) {
System.out.println("Group " + i + ": " + matcher.group(i));
}
}
测试结果
Full match:INFO 2020-03-31 10:12:00 QQ EVALUATE [张三] 6ebaf4ac780f40f486359f3ea6934620 "12355421" Beijing "116.4,39.5" [1200,15000,2100] "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36"
Group1:2020-03-31 10:12:00
Group2:QQ
Group3:EVALUATE
Group4:张三
Group5:6ebaf4ac780f40f486359f3ea6934620
Group6:12355421
Group7:Beijing
Group8:116.4,39.5
Group9:1200,15000,2100
Process finished with exit code 0
/**
* 2020-03-31 10:12:00 QQ EVALUATE [张三]
* 6ebaf4ac780f40f486359f3ea6934620 "12355421"
* Beijing "116.4,39.5" [1200,15000,2100]
* "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36"
* */
//评估类
public class EvaluateData implements Serializable {
private long evaluateTime;//评估时间
private String applicationName;//应用名
private String userIdentify;//用户的唯一标识
private String loginSequence;//登陆序列
private String ordernessPassword;//乱序密码
private String cityName;//城市名字
private GeoPoint geoPoint;//经纬度
private Double[] inputFeatures;//输入特征
private String deviceInformation;//登陆设备
- 提供set get 有参无参 toString方法
public class GeoPoint {
private double longtitude;//经度
private double latitude;//纬度
//登陆成功的数据
public class LoginSuccessData implements Serializable {
private long evaluateTime;//评估时间
private String applicationName;//应用名
private String userIdentify;//用户识别
private String loginSequence;
private String ordernessPassword;//
private String cityName;//城市名字
private GeoPoint geoPoint;//经纬度
private Double[] inputFeatures;//输入特征
private String deviceInformation;//登陆设备
/**
* 记录用户登陆的历史状态
* */
public class HistoryData implements Serializable {
//保存用户所登陆的历史城市
private Set<String> historyCities;
//历史的登陆设备信息
private List<String> historyDeviceInformations;
//当日登录的累计次数 -作为单独状态存储 不在update中实现(在流中记录当前用户登录的次数)
private Integer currentDayLoginCount;
//存储用户历史的乱序密码
private Set<String> historyOrdernessPasswords;
//用户输入特征更新 保留最近的前n个
private List<Double[]> latestInputFeatures;
//更新登录时间
private long lastLoginTime;
//上一次登录位置更新
private GeoPoint lastLoginGeoPoint;//经纬度
//历史登录的时间区间
private Map<String, Map<String, Integer>> historyLoginTimeSlot;
@Override
public String toString() {
return "HistoryData{" +
"historyCities=" + historyCities +
", historyDeviceInformations=" + historyDeviceInformations +
", currentDayLoginCount=" + currentDayLoginCount +
", historyOrdernessPasswords=" + historyOrdernessPasswords +
", latestInputFeatures=" + latestInputFeatures.stream().map(v-> Arrays.stream(v).map(a->a+"").reduce((v1,v2)->v1+","+v2).get()).collect(Collectors.toList()) +
", lastLoginTime=" + lastLoginTime +
", lastLoginGeoPoint=" + lastLoginGeoPoint +
", historyLoginTimeSlot=" + historyLoginTimeSlot +
'}';
}
public Set<String> getHistoryCities() {
return historyCities;
}
public void setHistoryCities(Set<String> historyCities) {
this.historyCities = historyCities;
}
public List<String> getHistoryDeviceInformations() {
return historyDeviceInformations;
}
public void setHistoryDeviceInformations(List<String> historyDeviceInformations) {
this.historyDeviceInformations = historyDeviceInformations;
}
public Integer getCurrentDayLoginCount() {
return currentDayLoginCount;
}
public void setCurrentDayLoginCount(Integer currentDayLoginCount) {
this.currentDayLoginCount = currentDayLoginCount;
}
public Set<String> getHistoryOrdernessPasswords() {
return historyOrdernessPasswords;
}
public void setHistoryOrdernessPasswords(Set<String> historyOrdernessPasswords) {
this.historyOrdernessPasswords = historyOrdernessPasswords;
}
public List<Double[]> getLatestInputFeatures() {
return latestInputFeatures;
}
public void setLatestInputFeatures(List<Double[]> latestInputFeatures) {
this.latestInputFeatures = latestInputFeatures;
}
public long getLastLoginTime() {
return lastLoginTime;
}
public void setLastLoginTime(long lastLoginTime) {
this.lastLoginTime = lastLoginTime;
}
public GeoPoint getLastLoginGeoPoint() {
return lastLoginGeoPoint;
}
public void setLastLoginGeoPoint(GeoPoint lastLoginGeoPoint) {
this.lastLoginGeoPoint = lastLoginGeoPoint;
}
public Map<String, Map<String, Integer>> getHistoryLoginTimeSlot() {
return historyLoginTimeSlot;
}
public void setHistoryLoginTimeSlot(Map<String, Map<String, Integer>> historyLoginTimeSlot) {
this.historyLoginTimeSlot = historyLoginTimeSlot;
}
}
public class EvalueateUtil {
//合法正则表达式
public static final String LEGAL_REGEX = "^INFO\\s(\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2})\\s([a-z0-9\\u4e00-\\u9fa5]*)\\s(EVALUATE|SUCCESS)\\s\\[([a-z0-9\\u4e00-\\u9fa5]*)\\]\\s([a-z0-9]{32})\\s\\\"([a-z0-9\\.\\-\\,]{6,12})\\\"\\s([a-z\\u4e00-\\u9fa5]*)\\s\\\"([0-9\\.\\,]*)\\\"\\s\\[([0-9\\,\\.]*)\\]\\s\\\"(.*)\\\"";
//评估正则表达式 只能是EVALUATE
public static final String EVALUATE_REGEX = "^INFO\\s(\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2})\\s([a-z0-9\\u4e00-\\u9fa5]*)\\s(EVALUATE)\\s\\[([a-z0-9\\u4e00-\\u9fa5]*)\\]\\s([a-z0-9]{32})\\s\\\"([a-z0-9\\.\\-\\,]{6,12})\\\"\\s([a-z\\u4e00-\\u9fa5]*)\\s\\\"([0-9\\.\\,]*)\\\"\\s\\[([0-9\\,\\.]*)\\]\\s\\\"(.*)\\\"";
//成功正则表达式
public static final String SUCCESS_REGEX = "^INFO\\s(\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2})\\s([a-z0-9\\u4e00-\\u9fa5]*)\\s(SUCCESS)\\s\\[([a-z0-9\\u4e00-\\u9fa5]*)\\]\\s([a-z0-9]{32})\\s\\\"([a-z0-9\\.\\-\\,]{6,12})\\\"\\s([a-z\\u4e00-\\u9fa5]*)\\s\\\"([0-9\\.\\,]*)\\\"\\s\\[([0-9\\,\\.]*)\\]\\s\\\"(.*)\\\"";
public static final Pattern LEGAL_PATTERN = Pattern.compile(LEGAL_REGEX, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
public static final Pattern EVALUATE_PATTERN = Pattern.compile(EVALUATE_REGEX, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
public static final Pattern SUCCESS_PATTERN = Pattern.compile(SUCCESS_REGEX, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
final static String regex = "^INFO\\s(\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2})\\s([a-z0-9\\u4e00-\\u9fa5]*)\\s(EVALUATE|SUCCESS)\\s\\[([a-z0-9\\u4e00-\\u9fa5]*)\\]\\s([a-z0-9]{32})\\s\\\"([a-z0-9\\.\\-\\,]{6,12})\\\"\\s([a-z\\u4e00-\\u9fa5]*)\\s\\\"([0-9\\.\\,]*)\\\"\\s\\[([0-9\\,\\.]*)\\]\\s\\\"(.*)\\\"";
final static String string = "INFO 2020-03-31 10:12:00 Q1Q应用1 success [张三] 6ebaf4ac780f40f486359f3ea6934620 \"123456\" Beijing \"116.4,39.5\" [1200,15000,2100] \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36\"";
//判断登陆是否合法调用此方法
public static Boolean isLegal(String input) {
Matcher matcher = LEGAL_PATTERN.matcher(input);
return matcher.matches();
}
//评估
public static Boolean isEvaluate(String input) {
Matcher matcher = EVALUATE_PATTERN.matcher(input);
return matcher.matches();
}
//是否是登陆成功的数据
public static Boolean isLoginSuccess(String input) {
Matcher matcher = SUCCESS_PATTERN.matcher(input);
return matcher.matches();
}
//
public static EvaluateData parseEvaluateData(String input) throws ParseException {
//指定一个验证数据对象
EvaluateData evaluateData = new EvaluateData();
Matcher matcher = EvalueateUtil.EVALUATE_PATTERN.matcher(input);
if (matcher.find()) {
//遍历
for (int i = 1; i <= matcher.groupCount(); i++) {
switch (i) {
case 1:
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse(matcher.group(i));
//传递时间参数
evaluateData.setEvaluateTime(date.getTime());
break;
case 2:
//设置应用名
evaluateData.setApplicationName(matcher.group(i));
break;
case 4:
//设置用户标识
evaluateData.setUserIdentify(matcher.group(i));
break;
case 5:
//设置应用序列
evaluateData.setLoginSequence(matcher.group(i));
break;
case 6:
//设置密码
evaluateData.setOrdernessPassword(matcher.group(i));
break;
case 7:
//设置城市
evaluateData.setCityName(matcher.group(i));
break;
case 8:
//设置经纬度
String geoparams = matcher.group(i);
String[] geos = geoparams.split(",");
//指定一个经纬度对象
GeoPoint geoPoint = new GeoPoint(Double.parseDouble(geos[0]), Double.parseDouble(geos[1]));
//设置位置对象
evaluateData.setGeoPoint(geoPoint);
break;
case 9:
//设置输入特性
String featrues = matcher.group(i);
String[] featureGroup = featrues.split(",");
Double[] doubleFeatrue = {
Double.parseDouble(featureGroup[0]),
Double.parseDouble(featureGroup[1]),
Double.parseDouble(featureGroup[2])
};
evaluateData.setInputFeatures(doubleFeatrue);
break;
case 10:
//设置设备信息
evaluateData.setDeviceInformation(matcher.group(i));
break;
}
}
}
return evaluateData;
}
//
public static LoginSuccessData parseLoginSuccessData(String input) throws ParseException {
//指定一个验证数据对象
LoginSuccessData loginSuccessData = new LoginSuccessData();
Matcher matcher = EvalueateUtil.SUCCESS_PATTERN.matcher(input);
if (matcher.find()) {
//遍历
for (int i = 1; i <= matcher.groupCount(); i++) {
switch (i) {
case 1:
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse(matcher.group(i));
//传递时间参数
loginSuccessData.setLoginTime(date.getTime());
break;
case 2:
//设置应用名
loginSuccessData.setApplicationName(matcher.group(i));
break;
case 4:
//设置用户标识
loginSuccessData.setUserIdentify(matcher.group(i));
break;
case 5:
//设置应用序列
loginSuccessData.setLoginSequence(matcher.group(i));
break;
case 6:
//设置密码
loginSuccessData.setOrdernessPassword(matcher.group(i));
break;
case 7:
//设置城市
loginSuccessData.setCityName(matcher.group(i));
break;
case 8:
//设置经纬度
String geoparams = matcher.group(i);
String[] geos = geoparams.split(",");
//指定一个经纬度对象
GeoPoint geoPoint = new GeoPoint(Double.parseDouble(geos[0]), Double.parseDouble(geos[1]));
//设置位置对象
loginSuccessData.setGeoPoint(geoPoint);
break;
case 9:
//设置输入特性
String featrues = matcher.group(i);
String[] featureGroup = featrues.split(",");
Double[] doubleFeatrue = {
Double.parseDouble(featureGroup[0]), Double.parseDouble(featureGroup[1]), Double.parseDouble(featureGroup[2])
};
loginSuccessData.setInputFeatures(doubleFeatrue);
break;
case 10:
//设置设备信息
loginSuccessData.setDeviceInformation(matcher.group(i));
break;
}
}
}
return loginSuccessData;
}
//获取应用名
public static String getApplicationName(String input) throws ParseException {
//获取匹配体
Matcher matcher = EvalueateUtil.LEGAL_PATTERN.matcher(input);
//如果匹配到了
if (matcher.find()) {
return matcher.group(2);
}
return null;
}
//获取用户名
public static String getUserIndetifiy(String input) throws ParseException {
//获取匹配体
Matcher matcher = EvalueateUtil.LEGAL_PATTERN.matcher(input);
//如果匹配到了
if (matcher.find()) {
return matcher.group(4);
}
return null;
}
final static Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
final static Matcher matcher = pattern.matcher(string);
public static void main(String[] args) throws ParseException {
//测试的数据
String input = "INFO 2020-03-31 10:12:00 Q1Q应用1 success [张三] 6ebaf4ac780f40f486359f3ea6934620 \"123456\" Beijing \"116.4,39.5\" [1200,15000,2100] \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36\"";
if (matcher.find()) {
System.out.println("Full match: " + matcher.group(0));
for (int i = 1; i <= matcher.groupCount(); i++) {
System.out.println("Group " + i + ": " + matcher.group(i));
}
}
LoginSuccessData loginSuccessData = parseLoginSuccessData(input);
System.out.println(loginSuccessData);
}
}
//评估接口
public abstract class Evaluate {
private RiskFactor riskFactor;
public Evaluate(RiskFactor riskFactor) {
this.riskFactor = riskFactor;
}
public RiskFactor getRiskFactor() {
return riskFactor;
}
/**
*
* @param evaluateData
* @param historyData
* @param evaluateReport
* @param evaluateChain:驱动下一个Evaluate实例
*/
//返回一个评估报告
public abstract void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport,EvaluateChain evaluateChain);
}
//评估报告
public class EvaluateReport implements Serializable {
private String applicationName;//应用名
private String userIdentify;//用户识别
private String loginSequence;
private long evaluateTime;//评估时间
private String cityName;//城市名字
private GeoPoint geoPoint;//经纬度
//报告单
private Map<RiskFactor,Boolean> metrics = new HashMap<RiskFactor, Boolean>();
public void signReport(RiskFactor riskFactor,boolean flag){
metrics.put(riskFactor,flag);
}
public EvaluateReport(String applicationName, String userIdentify, String loginSequence, long evaluateTime, String cityName, GeoPoint geoPoint) {
this.applicationName = applicationName;
this.userIdentify = userIdentify;
this.loginSequence = loginSequence;
this.evaluateTime = evaluateTime;
this.cityName = cityName;
this.geoPoint = geoPoint;
//初始化所有风险因子都是false
metrics.put(RiskFactor.AREA,false);
metrics.put(RiskFactor.DEVICE,false);
metrics.put(RiskFactor.SIMILARITY,false);
metrics.put(RiskFactor.SPEED,false);
metrics.put(RiskFactor.TIMESLOT,false);
metrics.put(RiskFactor.INPUTFEATURE,false);
metrics.put(RiskFactor.TOTAL,false);
}
/**
* AREA DEVICE INPUTFEATURE SIMILARITY SPEED TIMESLOT TOTAL
* QQ zhangsan 001 1585640451510 Beijing 116.4,39.5 true false false false true false false
* @return
*/
//覆盖toString生成报告单
@Override
public String toString(){
//排序
String report=metrics.keySet()
.stream()
.sorted((RiskFactor o1, RiskFactor o2) -> o1.name().compareTo(o2.name()))
.map(riskFactor -> metrics.get(riskFactor)+"")
.reduce((v1,v2)->v1+" "+v2)
.get();
// for (RiskFactor riskFactor : metrics.keySet()
// .stream()
// .sorted((RiskFactor o1, RiskFactor o2) -> o1.name().compareTo(o2.name())).collect(Collectors.toList())) {
// System.out.println(riskFactor);
// }
return applicationName+" "+userIdentify+" "+loginSequence+" "+evaluateTime+" "+cityName+ " "+geoPoint.getLongtitude()+","+geoPoint.getLatitude()+" "+report;
}
public static void main(String[] args) {
System.out.println( new EvaluateReport("QQ","zhangsan","001",new Date().getTime(),"Beijing",new GeoPoint(116.4,39.5)));
}
//声明所有的风险元素
public enum RiskFactor {
AREA("area"), //登陆地区
DEVICE("device"), //设备
TOTAL("total"), //累计登陆
TIMESLOT("timeslot"), //登陆时段
SIMILARITY("similarity"), //密码相似度
INPUTFEATURE("inputfeature"), //输入特征
SPEED("speed");//登陆位移速度
//风险的类型
private String name;
RiskFactor(String name){
this.name=name;
}
public class EvaluateChain {
private int position=0;
private List<Evaluate> evaluates;
public EvaluateChain(List<Evaluate> evaluates) {
this.evaluates = evaluates;
}
public void doChain(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport){
if(position < evaluates.size()){
//获取一个责任
Evaluate evaluate = evaluates.get(position);
position +=1;
evaluate.eval(evaluateData,historyData,evaluateReport,this);
}
}
}
评估数据: 当前登陆城市
历史数据: 留存用户曾经登陆过的城市Set集合
public class AreaEvaluate extends Evaluate {
public AreaEvaluate(RiskFactor riskFactor) {
super(riskFactor);
}
@Override
public void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport, EvaluateChain evaluateChain) {
evaluateReport.signReport(getRiskFactor(),doEval(evaluateData.getCityName(),historyData.getHistoryCities()));
//驱动调用下一个评估
evaluateChain.doChain(evaluateData,historyData,evaluateReport);
}
public boolean doEval(String cityName, Set<String> historyCities){
if(historyCities==null || historyCities.size()==0){
//说明是第一次使用
return false;
}else{//如果登陆过该城市就没有风险
return !historyCities.contains(cityName);
}
}
}
评估数据: 当前登陆设备
历史数据: 用户最近登陆过得N个设备-去重
public class DeviceEvaluate extends Evaluate {
public DeviceEvaluate() {
super(RiskFactor.DEVICE);
}
@Override
public void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport, EvaluateChain evaluateChain) {
//驱动调用下一个评估
evaluateChain.doChain(evaluateData,historyData,evaluateReport);
}
public boolean doEval(String deviceInformation, Set<String> historyDeviceInformations){
if(historyDeviceInformations==null || historyDeviceInformations.size()==0){
//说明第一次使用
return false;
}else{
//如果没有使用该设备
return !historyDeviceInformations.contains(deviceInformation);
}
}
}
评估数据: -
历史数据: 获取当天用户累计登陆次数
public class TotalEvaluate extends Evaluate {
private Integer threshold=0;
public TotalEvaluate(Integer threshold) {
super(RiskFactor.TOTAL);
this.threshold=threshold;
}
@Override
public void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport, EvaluateChain evaluateChain) {
//驱动调用下一个评估
evaluateChain.doChain(evaluateData,historyData,evaluateReport);
}
public boolean doEval(Integer currentDayLoginCount){
if(currentDayLoginCount==null){
//说明是第一次使用
return false;
}else{
return currentDayLoginCount>=threshold;
}
}
}
评估数据: 获取当前评估时间 dayOfWeek
hourOfDay
历史数据: 留存用户的所有的历史登陆数据 dayOfWeek
hourOfDay
count
需要从用户的历史登陆时段中 计算出哪些时段是用户的登陆习惯
public class TimeSlotEvaluate extends Evaluate {
private int threshold; //阈值
public TimeSlotEvaluate() {
super(RiskFactor.TIMESLOT);
}
@Override
public void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport, EvaluateChain evaluateChain) {
}
/**
* @param evaluateTime
* @param historyLoginTimeSlot
* @param threshold :设定多累计登录多少次以后再对用户进行评估
* @return
*/
public boolean doEval(long evaluateTime, Map<String, Map<String, Integer>> historyLoginTimeSlot, int threshold) {
String[] WEEKS = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(evaluateTime);
String dayOfWeek = WEEKS[calendar.get(Calendar.DAY_OF_WEEK) - 1];
DecimalFormat decimalFormat = new DecimalFormat("00");
String hourOfDay = decimalFormat.format(calendar.get(Calendar.HOUR_OF_DAY));//01 01...24
//用户第一次登陆
if (historyLoginTimeSlot == null || historyLoginTimeSlot.size() == 0) {
return false;
}
//用户是否到达评估计算阈值标准,如果登陆总次数小于阈值不做评估
if (!historyLoginTimeSlot.containsKey(dayOfWeek)) {
Integer totalCount = historyLoginTimeSlot.entrySet().stream() //星期几 Map<小时,次数>
.map(t -> t.getValue().entrySet().stream().map(v -> v.getValue()).reduce((v1, v2) -> v1 + v2).get())//每天登陆总数
.reduce((v1, v2) -> v1 + v2)
.get();
return totalCount >= threshold;
}else{
//获取当天的登陆时段数据
Map<String,Integer> historyTimeSlot=historyLoginTimeSlot.get(dayOfWeek);
if(!historyLoginTimeSlot.containsKey(hourOfDay)){
//该天登陆过,但是在该时段没有登陆
return true;
}else{
//该天登陆过 但是在该时段也登陆过
//判断当前的登陆时段是否是用户的登陆习惯
Integer currentHourLoginCount = historyTimeSlot.get(hourOfDay);
//升序登陆时间段集合
List<Integer> sortedList=historyTimeSlot.entrySet()
.stream().map(t->t.getValue())//每个时段登陆总和
.sorted().collect(Collectors.toList());
//获取用户登陆时段的阈值,大于或者等于该值都是习惯
Integer thresholdTimeSlotCount=sortedList.get((sortedList.size()*2)/3);
return currentHourLoginCount<thresholdTimeSlotCount;
/*
//计算出所有登录总次数大于thresholdTimeSlotCount值所有hourOfDay集合-习惯时段
List habbitTimeSlot = historyTimeSlot.entrySet()
.stream()
.filter(t -> t.getValue() >= thresholdTimeSlotCount)
.map(t -> t.getKey())
.collect(Collectors.toList());
return !habbitTimeSlot.contains(hourOfDay);
*/
}
}
}
}
评估数据: 获取乱序的密码
历史数据: 留存用户的所有的历史登陆乱序密码Set
public class SimilarityEvaluate extends Evaluate {
//定义一个阈值
private Double thresholdSimilarity;
public SimilarityEvaluate(Double thresholdSimilarity) {
super(RiskFactor.SIMILARITY);
this.thresholdSimilarity=thresholdSimilarity;
}
@Override
public void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport, EvaluateChain evaluateChain) {
evaluateReport.signReport(getRiskFactor(), doEval(evaluateData.getOrdernessPassword(), historyData.getHistoryOrdernessPasswords()));
//驱动调用下一个评估
evaluateChain.doChain(evaluateData, historyData, evaluateReport);
}
public boolean doEval(String ordernessPassword, Set<String> historyOrdernessPasswords) {
if (historyOrdernessPasswords == null || historyOrdernessPasswords.size() == 0) {
return false;
}
if (historyOrdernessPasswords.contains(ordernessPassword)) {
return false;
}
//构建词袋模型-密码维度
Set<Character> chars = new HashSet<>();
//迭代遍历
historyOrdernessPasswords.stream().forEach(historyOrdernessPassword -> {
for (char c : historyOrdernessPassword.toCharArray()) {
chars.add(c);
}
});
//将现行密码加入词袋,富华维度
for (char c : ordernessPassword.toCharArray()) {
chars.add(c);
}
//对词袋进行排序
List<Character> wordBag = chars.stream().sorted().collect(Collectors.toList());
System.out.println(wordBag);
//将所有的历史密码转换成向量
List<Integer[]> verctors = historyOrdernessPasswords.stream()
.map(historyOrdernessPassword -> converString2Vector(wordBag, historyOrdernessPassword))
.collect(Collectors.toList());
//当前输入密码的特征向量
Integer[] currentVector = converString2Vector(wordBag, ordernessPassword);
//计算相似度
List<Double> resultList = verctors.stream()
.map(v1 -> {
//当前密码和我们的相似度多少
double similarity = calculateSimilarity(v1, currentVector);
System.out.println(similarity);
return similarity;
})
.filter(similarity -> similarity >= thresholdSimilarity)
.collect(Collectors.toList());
//有风险
return resultList.size()==0;
}
private double calculateSimilarity(Integer[] v1,Integer[] v2){
//分母-v1,v2对应位置的元素的乘积求和
Double sum=0.0;
for (int i = 0; i < v1.length; i++) {
sum+=v1[i]*v2[i];
}
//计算平方和两个向量的平方和开根号
Integer powSum1 = Arrays.stream(v1).map(i -> i * i).reduce((i1, i2) -> i1 + i2).get();
Integer powSum2 = Arrays.stream(v1).map(i -> i * i).reduce((i1, i2) -> i1 + i2).get();
return sum/(Math.sqrt(powSum1)*Math.sqrt(powSum2));
}
//将密码转换成向量
private Integer[] converString2Vector(List<Character> wordBag, String ordernessPassword) {
//词袋多长向量就多大
Integer[] vector = new Integer[wordBag.size()];
HashMap<Character, Integer> charMap = new HashMap<>();
for (char c : ordernessPassword.toCharArray()) {
Integer count = 1;
//如果不包含
if (charMap.containsKey(c)) {
count += charMap.get(c);
}
charMap.put(c, count);
}
//迭代遍历
for (int i = 0; i < wordBag.size(); i++) {
Character c = wordBag.get(i);
vector[i] = charMap.containsKey(c) ? charMap.get(c) : 0;
}
return vector;
}
public static void main(String[] args) {
SimilarityEvaluate similarityEvaluate = new SimilarityEvaluate(0.7);
HashSet<String> pwds = new HashSet<>();
pwds.add("456vcft");
pwds.add("ct12345");
pwds.add("hjk8901");
System.out.println(similarityEvaluate.doEval("123456ct", pwds));
}
}
测试结果
[0, 1, 2, 3, 4, 5, 6, 8, 9, c, f, h, j, k, t, v]
0.7142857142857142
0.9999999999999999
0.14285714285714285
false
使用到的公式-余弦相似度公式:
评估数据:获取输入特征
历史数据:留存用户最近10次输入特征
import com.baizhi.entity.EvaluateData;
import com.baizhi.entity.EvaluateReport;
import com.baizhi.entity.HistoryData;
import com.baizhi.entity.RiskFactor;
import com.baizhi.evaluate.Evaluate;
import com.baizhi.evaluate.EvaluateChain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class InputfeatureEvaluate extends Evaluate {
public InputfeatureEvaluate() {
super(RiskFactor.INPUTFEATURE);
}
@Override
public void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport, EvaluateChain evaluateChain) {
evaluateReport.signReport(getRiskFactor(), doEval(evaluateData.getInputFeatures(), historyData.getLatestInputFeatures()));
//驱动调用下一个评估
evaluateChain.doChain(evaluateData, historyData, evaluateReport);
}
/**
* 1.计算圆心中点
* 2.计算两两特征距离
* 3.对距离进行排序(升序),取2/3处作为评估距离阈值 - threshold
* 4.计算当前输入的特征距离中心点距离d
*
* @param inputFeatures
* @param latestInputFeatures
* @return
*/
public boolean doEval(Double[] inputFeatures, List<Double[]> latestInputFeatures) {
//至少保证历史记录有2条(含)以上
if (latestInputFeatures == null || latestInputFeatures.size() <2) {
//说明是第一次使用
return false;
}
//1.计算圆心中点
Double[] sumInputs = latestInputFeatures.stream().reduce((v1, v2) -> {
Double[] sumInputFeatures = new Double[v1.length];
for (int i = 0; i < v1.length; i++) {
if (sumInputFeatures[i] == null) {
sumInputFeatures[i] = 0.0;
}
sumInputFeatures[i] += v1[i] + v2[i];
}
return sumInputFeatures;
}).get();
Double[] centerVector = new Double[sumInputs.length];
for (int i = 0; i < sumInputs.length; i++) {
centerVector[i] = sumInputs[i] / latestInputFeatures.size();
}
System.out.println("中点:" + Arrays.stream(centerVector).map(i -> i + "").reduce((v1, v2) -> v1 + "," + v2).get());
//计算两两特征距离
List<Double> distances = new ArrayList<>();
//一定可以拿到n*(n-1)/2 [n=latestInputFeatures.size()]
for (int i = 0; i < latestInputFeatures.size(); i++) {
Double[] currentFeature = latestInputFeatures.get(i);
for (int j = i + 1; j < latestInputFeatures.size(); j++) {
Double[] nextFeature = latestInputFeatures.get(j);
//计算向量距离
Double distance = calculateDistance(currentFeature, nextFeature);
distances.add(distance);
}
}
//3.对距离进行排序(升序) 取2/3作为评估距离阈值 -threshold
distances.sort(new Comparator<Double>() {
@Override
public int compare(Double o1, Double o2) {
if (o1 == o2) {
return 0;
} else {
return o1 > o2 ? 1 : -1;
}
}
});
System.out.println("distances:" + distances);
Integer n = latestInputFeatures.size();
int position = (n * (n - 1) / 2) * 2 / 3;
Double thresholdDistance = distances.get(position);
//4.计算当前输入的特征距离中心点距离d
Double currentDistance = calculateDistance(inputFeatures, centerVector);
System.out.println("threshold:" + thresholdDistance + "," + "currentDistance:" + centerVector);
return currentDistance > thresholdDistance;
}
private Double calculateDistance(Double[] v1, Double[] v2) {
Double sum = 0.0;
for (int i = 0; i < v1.length; i++) {
sum += (v1[i] - v2[i]) * (v1[i] - v2[i]);
}
return Math.sqrt(sum);
}
public static void main(String[] args) {
InputfeatureEvaluate inputFeatureEvaluate = new InputfeatureEvaluate();
ArrayList<Double[]> latestInputFeatures = new ArrayList<>();
latestInputFeatures.add(new Double[]{1000.0, 1100.0, 1800.0});
latestInputFeatures.add(new Double[]{1100.0, 1120.0, 1750.0});
latestInputFeatures.add(new Double[]{950.0, 1250.0, 2000.0});
latestInputFeatures.add(new Double[]{1200.0, 1050.0, 1900.0});
latestInputFeatures.add(new Double[]{1400.0, 800.0, 2500.0});
inputFeatureEvaluate.doEval(new Double[]{1100.0, 1000.0, 1750.0}, latestInputFeatures);
}
}
使用到数学公式:欧式距离
公式推导:
评估数据:评估时间,GeoPoint
历史数据:上一次登录的时间,上一次登录GeoPoint
import com.baizhi.entity.*;
import com.baizhi.evaluate.Evaluate;
import com.baizhi.evaluate.EvaluateChain;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import static java.lang.Math.*;
public class SpeedEvaluate extends Evaluate {
private Double thresholdSpeed;
private static final Double EARTH_RADIUS=6371.393;//千米
public SpeedEvaluate(Double thresholdSeep) {
super(RiskFactor.SPEED);
this.thresholdSpeed=thresholdSeep;
}
@Override
public void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport, EvaluateChain evaluateChain) {
evaluateReport.signReport(getRiskFactor(),doEval(evaluateData.getEvaluateTime(),evaluateData.getGeoPoint(),historyData.getLastLoginTime(),historyData.getLastLoginGeoPoint()));
//驱动调用下一个评估
evaluateChain.doChain(evaluateData,historyData,evaluateReport);
}
public boolean doEval(long evaluateTime, GeoPoint currentGeoPoint,long lastLoginTime,GeoPoint lastLgoinGeoPoint){
if(lastLgoinGeoPoint==null){
return false;
}
//计算时差
double diffTime=(evaluateTime-lastLoginTime)*1.0/(3600*1000);//小时时差
Double distance = calculateDistance(currentGeoPoint, lastLgoinGeoPoint);
double speed = distance/ diffTime;
System.out.println("distance:"+distance+",diffTime:"+diffTime+",speed:"+speed);
return speed>thresholdSpeed;
}
//角度转弧度
private Double calculateDistance(GeoPoint point1,GeoPoint point2){
//精度长 纬度短
Double wA=toRadians(point1.getLatitude());//将角度转换为弧度
Double jA=toRadians(point1.getLongtitude());
Double wB=toRadians(point2.getLatitude());
Double jB=toRadians(point2.getLongtitude());
//计算弧度 反余弦值
return EARTH_RADIUS*acos(cos(wA)*cos(wB)+cos(jB-jA)+sin(wA)*sin(wB));
}
public static void main(String[] args) throws ParseException {
SpeedEvaluate speedEvaluate = new SpeedEvaluate(600.0);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
long evaluateTime=sdf.parse("2020-04-01 10:10:00").getTime();
GeoPoint p1=new GeoPoint();
p1.setLongtitude(116.2317);//北京
p1.setLatitude(39.5427);
long lastLoginTime=sdf.parse("2020-04-01 08:00:00").getTime();
GeoPoint p2=new GeoPoint();
p2.setLongtitude(114.14);//郑州
p2.setLatitude(34.16);
speedEvaluate.doEval(evaluateTime,p1,lastLoginTime,p2);
}
}
数学计算公式:球面距离