• Flink模拟项目: 恶意登录监控


    4.1 模块创建和数据准备

    继续在UserBehaviorAnalysis下新建一个 maven module作为子项目,命名为LoginFailDetect。在这个子模块中,我们将会用到flink的CEP库来实现事件流的模式匹配,所以需要在pom文件中引入CEP的相关依赖:

    <dependency>
            <groupId>org.apache.flink</groupId>
    <artifactId>flink-cep_${scala.binary.version}</artifactId>
    <version>${flink.version}</version>
    </dependency>
    <dependency>
            <groupId>org.apache.flink</groupId>
    <artifactId>flink-cep-scala_${scala.binary.version}</artifactId>
    <version>${flink.version}</version>
    </dependency>
    

      同样,在src/main/目录下,将默认源文件目录java改名为scala。

    4.2 代码实现

    对于网站而言,用户登录并不是频繁的业务操作。如果一个用户短时间内频繁登录失败,就有可能是出现了程序的恶意攻击,比如密码暴力破解。因此我们考虑,应该对用户的登录失败动作进行统计,具体来说,如果同一用户(可以是不同IP)在2秒之内连续两次登录失败,就认为存在恶意登录的风险,输出相关的信息进行报警提示。这是电商网站、也是几乎所有网站风控的基本一环。

    4.2.1 状态编程

    由于同样引入了时间,我们可以想到,最简单的方法其实与之前的热门统计类似,只需要按照用户ID分流,然后遇到登录失败的事件时将其保存在ListState中,然后设置一个定时器,2秒后触发。定时器触发时检查状态中的登录失败事件个数,如果大于等于2,那么就输出报警信息。

    在src/main/scala下创建LoginFail.scala文件,新建一个单例对象。定义样例类LoginEvent,这是输入的登录事件流。由于没有现成的登录数据,我们用几条自定义的示例数据来做演示。

    代码如下:

    case class LoginEvent(userId: Long, ip: String, eventType: String, eventTime: Long)
    object LoginFail {
    
      def main(args: Array[String]): Unit = {
    
        val env = StreamExecutionEnvironment.getExecutionEnvironment
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
        env.setParallelism(1)
    
        val loginEventStream = env.fromCollection(List(
          LoginEvent(1, "192.168.0.1", "fail", 1558430842),
          LoginEvent(1, "192.168.0.2", "fail", 1558430843),
          LoginEvent(1, "192.168.0.3", "fail", 1558430844),
          LoginEvent(2, "192.168.10.10", "success", 1558430845)
        ))
          .assignAscendingTimestamps(_.eventTime * 1000)
          .keyBy(_.userId)
          .process(new MatchFunction())
          .print()
    
        env.execute("Login Fail Detect Job")
      }
    
      class MatchFunction extends KeyedProcessFunction[Long, LoginEvent, LoginEvent] {
    
        // 定义状态变量
    lazy val loginState: ListState[LoginEvent] = getRuntimeContext.getListState(
          new ListStateDescriptor[LoginEvent]("saved login", classOf[LoginEvent]))
    
        override def processElement(login: LoginEvent,
                                  context: KeyedProcessFunction[Long, LoginEvent, 
    LoginEvent]#Context, out: Collector[LoginEvent]): Unit = {
    
          if (login.eventType == "fail") {
            loginState.add(login)
          }
          // 注册定时器,触发事件设定为2秒后
          context.timerService.registerEventTimeTimer(login.eventTime + 2 * 1000)
        }
    
        override def onTimer(timestamp: Long,
                        ctx: KeyedProcessFunction[Long, LoginEvent, 
    LoginEvent]#OnTimerContext, out: Collector[LoginEvent]): Unit = {
    
    
          val allLogins: ListBuffer[LoginEvent] = ListBuffer()
          import scala.collection.JavaConversions._
          for (login <- loginState.get) {
            allLogins += login
          }
          loginState.clear()
    
          if (allLogins.length > 1) {
            out.collect(allLogins.head)
          }
        }
      }
    }

    4.2.2 CEP编程

    上一节的代码实现中我们可以看到,直接把每次登录失败的数据存起来、设置定时器一段时间后再读取,这种做法尽管简单,但和我们开始的需求还是略有差异的。这种做法只能隔2秒之后去判断一下这期间是否有多次失败登录,而不是在一次登录失败之后、再一次登录失败时就立刻报警。这个需求如果严格实现起来,相当于要判断任意紧邻的事件,是否符合某种模式。这听起来就很复杂了,那有什么方式可以方便地实现呢?

    很幸运,flink为我们提供了CEP(Complex Event Processing,复杂事件处理)库,用于在流中筛选符合某种复杂模式的事件。接下来我们就基于CEP来完成这个模块的实现。

    在src/main/scala下继续创建LoginFailWithCep.scala文件,新建一个单例对象。样例类LoginEvent由于在LoginFail.scala已经定义,我们在同一个模块中就不需要再定义了。

    object LoginFailWithCep {
    
      def main(args: Array[String]): Unit = {
    
        val env = StreamExecutionEnvironment.getExecutionEnvironment
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
        env.setParallelism(1)
    
        val loginEventStream = env.fromCollection(List(
          LoginEvent(1, "192.168.0.1", "fail", 1558430842),
          LoginEvent(1, "192.168.0.2", "fail", 1558430843),
          LoginEvent(1, "192.168.0.3", "fail", 1558430844),
          LoginEvent(2, "192.168.10.10", "success", 1558430845)
        )).assignAscendingTimestamps(_.eventTime * 1000)
    
    // 定义匹配模式
        val loginFailPattern = Pattern.begin[LoginEvent]("begin")
          .where(_.eventType == "fail")
          .next("next")
          .where(_.eventType == "fail")
          .within(Time.seconds(2))
    
    // 在数据流中匹配出定义好的模式
        val patternStream = CEP.pattern(loginEventStream.keyBy(_.userId), loginFailPattern)
    
        // .select方法传入一个 pattern select function,当检测到定义好的模式序列时就会调用
        val loginFailDataStream = patternStream
          .select((pattern: Map[String, Iterable[LoginEvent]]) => {
            val first = pattern.getOrElse("begin", null).iterator.next()
            val second = pattern.getOrElse("next", null).iterator.next()
            (second.userId, second.ip, second.eventType)
          })
        // 将匹配到的符合条件的事件打印出来
        loginFailDataStream.print()
        env.execute("Login Fail Detect Job")
      }
    }
    

      

  • 相关阅读:
    Ant的实现原理
    单例模式
    Ant常用代码段
    [转]大象吃香蕉问题
    i++和++i探秘
    带滚动条的table
    公钥系统/数字签名/数字证书工作原理入门 [转]
    动态创建WPF 控件,并绑定指定Style
    C#如何使用帮助及如何关联到F1键
    Umbraco安装记录
  • 原文地址:https://www.cnblogs.com/tesla-turing/p/13276487.html
Copyright © 2020-2023  润新知