用户综合分析系统:大数据板块---登录风险评估(评估因子篇)

登陆风险评估

用户综合分析系统:大数据板块---登录风险评估(评估因子篇)_第1张图片
检查出⾮⽤户登录,或者账号被盗窃等潜在⻛险挖掘。通过对⽤户登录⾏为进⾏分析,提⾼了预测的准确性;可以应⽤于互联⽹⾦融⻛控技术中,也可应⽤于普通⽹站的⽤户恶意登录识别技术中。

  • 异地登录认定有⻛险(不在⽤户经常登陆地)
  • 登录的位移速度超过正常值,认定有⻛险
  • 更换设备,认定有⻛险
  • 登录时段-习惯,出现了异常,存在⻛险
  • 每天的累计登录次数超过上限,认定有⻛险
  • 密码输⼊错误或者输⼊差异性较⼤,认定⻛险
  • 如果⽤户的输⼊特征(输⼊每个控件所需时⻓ ms)发⽣变化

算法实现概述

⼀种基于⽤户登录⾏为分析的⻛控⽅法,其特征在于:所述的⽅法包括 ⽤户按键⻛险识别⽤户登录地⻛险识别密码重试⻛险识别设备来源⻛险识别⽤户登录习惯⻛险识别累计登录多次⻛险识别⽤户登录的瞬时位移速度⻛险识别 七种模型;本系统仅仅负责根据⽤户的登录数据产⽣⻛险评估报告。报告的格式为:
用户综合分析系统:大数据板块---登录风险评估(评估因子篇)_第2张图片

日志格式

并不负责对⽤户的登录做出定性的分析,仅仅当系统发送⽤户登录数据过来,由⼤数据评估系统对⽤户数据进⾏评估,然后产⽣评估报告。由业务系统⾃⾏根据评估报告,由⽤户采取相关的奖惩措施。介于以上评估报告系统需要⽤户发送以下数据格式,以辅助系统完成验证:

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 SUCCESS [张三] 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"

数据提取

借助:https://regex101.com

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 class EvaluateUtil {
    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\\\"(.*)\\\"";
    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 = EvaluateUtil.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[] doubleFertrue = {Double.parseDouble(featureGroup[0]),Double.parseDouble(featureGroup[1]),Double.parseDouble(featureGroup[2])};
                        evaluateData.setInputFeatures(doubleFertrue);
                        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 = EvaluateUtil.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[] doubleFertrue = {Double.parseDouble(featureGroup[0]),Double.parseDouble(featureGroup[1]),Double.parseDouble(featureGroup[2])};
                        loginSuccessData.setInputFeatures(doubleFertrue);
                        break;
                    case 10:
                        //设置设备信息
                        loginSuccessData.setDeviceInformation(matcher.group(i));
                        break;
                }
            }

        }
        return  loginSuccessData;
    }

    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);

    }
}

Full match: INFO 2020-03-31 10:12:00 QQ 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"
Group 1: 2020-03-31 10:12:00
Group 2: QQ
Group 3: SUCCESS
Group 4: 张三
Group 5: 6ebaf4ac780f40f486359f3ea6934620
Group 6: 123456
Group 7: Beijing
Group 8: 116.4,39.5
Group 9: 1200,15000,2100
Group 10: 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
评估因⼦实现
  • 异地登陆认定是风险(☆)

评估数据:当前登陆城市 beijing
历史数据 : 留存用户的历史登陆城市一个Set集合[ beijing , shanghai , zhengzhou ]

package com.baizhi.evaluate.impl;

import com.baizhi.entities.EvaluateData;
import com.baizhi.entities.EvaluateReport;
import com.baizhi.entities.HistoryData;
import com.baizhi.entities.RiskFactor;
import com.baizhi.evaluate.Evaluate;
import com.baizhi.evaluate.EvaluateChain;

import java.util.Set;

public class AreaEvaluate extends Evaluate {
    public AreaEvaluate() {
        super(RiskFactor.AREA);
    }

    @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);
        }
    }
}

  • 更换设备存在风险(☆)

评估数据:当前用户的设备信息
历史数据 : 仅仅保存近期的3个有效设备信息 去重

package com.baizhi.evaluate.impl;

import com.baizhi.entities.EvaluateData;
import com.baizhi.entities.EvaluateReport;
import com.baizhi.entities.HistoryData;
import com.baizhi.entities.RiskFactor;
import com.baizhi.evaluate.Evaluate;
import com.baizhi.evaluate.EvaluateChain;

import java.util.Set;

public class DeviceEvaluate extends Evaluate {
    public DeviceEvaluate() {
        super(RiskFactor.DEVICE);
    }

    @Override
    public void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport, EvaluateChain evaluateChain) {
        evaluateReport.signReport(getRiskFactor(),doEval(evaluateData.getDeviceInformation(),historyData.getHistoryDeviceInformations()));
        //驱动调用下一个评估
        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);
        }
    }
}

  • 登陆次数累积超过5次/每天(☆)

评估数据:-
历史数据:当天上一次累积评估次数

package com.baizhi.evaluate.impl;

import com.baizhi.entities.EvaluateData;
import com.baizhi.entities.EvaluateReport;
import com.baizhi.entities.HistoryData;
import com.baizhi.entities.RiskFactor;
import com.baizhi.evaluate.Evaluate;
import com.baizhi.evaluate.EvaluateChain;

import java.util.Set;

public class TotalEvaluate extends Evaluate {
    private Integer threshold=0;

    /**
     * 允许用户最大的登录次数
     * @param threshold
     */
    public TotalEvaluate(Integer threshold) {
        super(RiskFactor.TOTAL);
        this.threshold=threshold;
    }

    @Override
    public void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport, EvaluateChain evaluateChain) {
        evaluateReport.signReport(getRiskFactor(),doEval(historyData.getCurrentDayLoginCount()));
        //驱动调用下一个评估
        evaluateChain.doChain(evaluateData,historyData,evaluateReport);
    }

    public boolean doEval(Integer currentDayLoginCount){
        if(currentDayLoginCount==null){//说明是第一次使用
            return false;
        }else{//如果登录超过限定次数
            return currentDayLoginCount >= threshold;
        }
    }
}

  • 用户的登陆时段(习惯)存在异常(☆☆)

评估数据:当前登陆时间,提取 dayOfWeek hourOfDay
历史数据:留存用户所有的历史登陆信息 dayOfWeek hourOfDay 次数

public class TimeSlotEvaluate extends Evaluate {
    private int threshold;
    public TimeSlotEvaluate(int threshold) {//设定多累计登录多少次以后再对用户进行评估
        super(RiskFactor.TIMESLOT);
        this.threshold=threshold;
    }

    @Override
    public void eval(EvaluateData evaluateData, HistoryData historyData, EvaluateReport evaluateReport, EvaluateChain evaluateChain) {
        evaluateReport.signReport(getRiskFactor(),doEval(evaluateData.getEvaluateTime(),historyData.getHistoryLoginTimeSlot()));
        evaluateChain.doChain(evaluateData,historyData,evaluateReport);
    }

    /**
     * @param evaluateTime
     * @param historyLoginTimeSlot
     * @return
     */
    public boolean doEval(long evaluateTime, Map<String, Map<String,Integer>> historyLoginTimeSlot){
        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 02 ... 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(!historyTimeSlot.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);
                */
            }
        }
    }
}

  • 密码错误评估,成分评估(☆☆☆)
    此评估的依据需要借助余弦相似度来进行评估,因为普通的java代码不能够满足评估的需求,故需要进行建模,将用户的历史密码以及当前输入的密码进行提取特征,构建成特征向量,然后根据两两向量之间夹角的余弦值进行评定,余弦值越小说明相似度越高。

    余弦相似度的具体介绍请看:https://blog.csdn.net/weixin_45607513/article/details/105245121

评估数据:乱序明文密码
历史数据:历史明文密码乱序集合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;
      }
      //构建词袋-密码维度
      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:"+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){
        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(v2).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<>();
        //如果包含字符,加1;不包含,置为1
        for (Character 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;

    }
  • 登陆的位移速度 距离/时间(☆☆☆☆)
    根据经纬度计算两个登录地点的距离,进而计算所需要的速度,给定一个阈值。将速度与此阈值进行比较,若果大于阈值则说明不符合现实逻辑,有风险。

评估数据:评估时间、GeoPoint
历史数据: 上⼀次登录的时间、上⼀次登录GeoPoint

public class SpeedEvaluate extends Evaluate {
    //阈值
    private Double thresholdSpeed;
    //地球半径
    private static final Double EARTH_RADIUS=6371.393;//千米

    public SpeedEvaluate(Double thresholdSpeed) {
        super(RiskFactor.SPEED);
        this.thresholdSpeed=thresholdSpeed;
    }

    @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 lastLoginGeoPoint){
        //第一次登录
        if(lastLoginGeoPoint==null){
            return false;
        }
        //两次登录的时间差
        double diffTime=(evaluateTime-lastLoginTime)*1.0/(3600*1000);//小时时差
        //两次登录的距离
        Double distance = calculateDistance(currentGeoPoint, lastLoginGeoPoint);
        //之间的速度
        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);
    }
}
  • 采集用户的输入特征(每个控件输入时长)(☆☆☆☆)

评估数据:获取⽤户输⼊特征 [a0,b0,c0]
历史数据: 留存⽤户最近10次输⼊特征[(a1,b1,c1),(a2,b2,c2),(a3,b3,c3),…]

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());
        //2.计算两两特征距离
        List<Double> distances=new ArrayList<>();
        //一定可以拿到 n*(n-1)/2  【n= latestInputFeatures.size()】 如:5个点,一共有10个距离
        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:"+currentDistance);
        //如果大于阈值,说明有风险
        return currentDistance> thresholdDistance;
    }
    //欧氏距离  n维空间  两个坐标点的距离
    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);
    }
}

关于余弦相似度、欧式距离、球面距离公式的推断可以参考以前的博客:
余弦相似度:https://blog.csdn.net/weixin_45607513/article/details/105245121
欧氏距离:https://blog.csdn.net/weixin_45607513/article/details/105266407
球面距离公式:https://blog.csdn.net/weixin_45607513/article/details/105275046

你可能感兴趣的:(用户综合分析系统)