项目实施(一)-大数据板块设计之用户登录风险评估

大数据板块设计(阶段2)

用户登陆风险评估

检查出⾮⽤户登录,或者账号被盗窃等潜在⻛险挖掘。 通过对用户登录行为进行分析,提高了预测的准确性;可以应用于互联网金融风控技术中,也可应用于普通网站的用户恶意登录识别技术中

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

参考:https://zhuanli.tianyancha.com/796c88e9c3d67d3a9c59716558818e22

项目实施(一)-大数据板块设计之用户登录风险评估_第1张图片

算法实现概述

一种基于用户登录行为分析的风控方法,其特征在于:所述的方法包括用户按键风险识别用户登录地风险识别密码重试风险识别设备来源风险识别 用户登陆习惯风险识别 累计登陆多次风险识别 用户登陆的瞬时位移速度风险识别其中模型;本系统仅仅负责根据用户的登陆数据产生风险评估报告.报告的格式为:

应用信息 用户唯一标识 登陆地区 经纬度 登陆序列号 评估时间 输入特征 地区 速度 设备 习惯 次数 密码
QQ 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;
    }
}

  • 定义一个Evaluate工具类
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);
        }
    }
}

当天累计登陆N次评估()

评估数据: -

历史数据: 获取当天用户累计登陆次数

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

使用到的公式-余弦相似度公式:

项目实施(一)-大数据板块设计之用户登录风险评估_第2张图片

推导过程-一维 二维 三维
项目实施(一)-大数据板块设计之用户登录风险评估_第3张图片
项目实施(一)-大数据板块设计之用户登录风险评估_第4张图片
项目实施(一)-大数据板块设计之用户登录风险评估_第5张图片

用户输入特性评估()

评估数据:获取输入特征

历史数据:留存用户最近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);
    }
}

使用到数学公式:欧式距离

公式推导:

项目实施(一)-大数据板块设计之用户登录风险评估_第6张图片

登录位移速度评估()

评估数据:评估时间,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);
    }
}

数学计算公式:球面距离

项目实施(一)-大数据板块设计之用户登录风险评估_第7张图片

你可能感兴趣的:(项目实施(一)-大数据板块设计之用户登录风险评估)