kafka 2.4 新版 Java Authorizer API解析

背景

    kafka 2.4 的release note 除了引入MirrorMaker 2.0 之外,还有一项改动是引入了新的java版的认证API接口。原因是这样的:
    kafka使用scla版的kafka.security.auth.Authorizer的trait来支持可插拔的授权接口。 但是KIP-50中已经同意用“client”模块中的org.apache.kafka.common包中的Java接口和Java ACL类来替换了它,但是之前一直都没有合并。于是社区就就添加了新版的java 授权接口, 这些Java接口能提供比Scala Trait更好的兼容性,能让开发者更容易开发自己的认证模块。

接口方法解析

    新版接口的工作流程是这样的:

  1. 判断是否配置了"authorizer.class.name",如果配置了就实例化一个Authorizer接口
  2. 调用configure()和start()方法,初始化Authorizer接口对应的实现类里的元信息
  3. broker启动socketServer接收请求,调用authorize()方法来处理每一个请求

    如果在配置中实现了Reconfigurable这个类的话还可以动态变更Authorizer 的实现类而不用重启broker,还有三点需要注意的是:

  1. 所有的授权操作和Acl更新必须是线程安全的
  2. Acl 更新操作是异步的,
  3. 也可以在start()方法中定义其他的需要用的线程或者线程池,但是切记在close()方法里关掉他们

    除了上面提到的那些方法,还有下面三个方法
createAcls()和deleteAcls():用来添加和删除Acl
acls():用来获取符合查询条件的Acl

@InterfaceStability.Evolving
public interface Authorizer extends Configurable, Closeable {
    Map<Endpoint, ? extends CompletionStage<Void>> start(AuthorizerServerInfo serverInfo);
    List<AuthorizationResult> authorize(AuthorizableRequestContext requestContext, List<Action> actions);
    List<? extends CompletionStage<AclCreateResult>> createAcls(AuthorizableRequestContext requestContext, List<AclBinding> aclBindings);
    List<? extends CompletionStage<AclDeleteResult>> deleteAcls(AuthorizableRequestContext requestContext, List<AclBindingFilter> aclBindingFilters);
    Iterable<AclBinding> acls(AclBindingFilter filter);
}

实现类AclAuthorizer解析

kafka2.4中同样提供了一个"SimpleAclAuthorizer"的实现,只不过这次它去掉了Simple,就叫"AclAuthorizer",功能和SimpleAclAuthorizer基本一样,只是实现方式变了,可以看下具体的方法:

start方法

    好像什么也没干

  override def start(serverInfo: AuthorizerServerInfo): util.Map[Endpoint, _ <: CompletionStage[Void]] = {
    serverInfo.endpoints.asScala.map { endpoint =>
      endpoint -> CompletableFuture.completedFuture[Void](null) }.toMap.asJava
  }

configure方法

    在configure方法里读取了kafka的配置,生成了zk的客户端和aclChangeListeners(用来感知zk节点变化,若有变化,则更新acl的cache),并通过loadCache()加载了zk中已经有 的权限。

  override def configure(javaConfigs: util.Map[String, _]): Unit = {
    val configs = javaConfigs.asScala
    val props = new java.util.Properties()
    configs.foreach { case (key, value) => props.put(key, value.toString) }

    superUsers = configs.get(AclAuthorizer.SuperUsersProp).collect {
      case str: String if str.nonEmpty => str.split(";").map(s => SecurityUtils.parseKafkaPrincipal(s.trim)).toSet
    }.getOrElse(Set.empty[KafkaPrincipal])

    shouldAllowEveryoneIfNoAclIsFound = configs.get(AclAuthorizer.AllowEveryoneIfNoAclIsFoundProp).exists(_.toString.toBoolean)

    // Use `KafkaConfig` in order to get the default ZK config values if not present in `javaConfigs`. Note that this
    // means that `KafkaConfig.zkConnect` must always be set by the user (even if `AclAuthorizer.ZkUrlProp` is also
    // set).
    val kafkaConfig = KafkaConfig.fromProps(props, doLog = false)
    val zkUrl = configs.get(AclAuthorizer.ZkUrlProp).map(_.toString).getOrElse(kafkaConfig.zkConnect)
    val zkConnectionTimeoutMs = configs.get(AclAuthorizer.ZkConnectionTimeOutProp).map(_.toString.toInt).getOrElse(kafkaConfig.zkConnectionTimeoutMs)
    val zkSessionTimeOutMs = configs.get(AclAuthorizer.ZkSessionTimeOutProp).map(_.toString.toInt).getOrElse(kafkaConfig.zkSessionTimeoutMs)
    val zkMaxInFlightRequests = configs.get(AclAuthorizer.ZkMaxInFlightRequests).map(_.toString.toInt).getOrElse(kafkaConfig.zkMaxInFlightRequests)

    val zkClientConfig = AclAuthorizer.zkClientConfigFromKafkaConfigAndMap(kafkaConfig, configs)
    val time = Time.SYSTEM
    zkClient = KafkaZkClient(zkUrl, kafkaConfig.zkEnableSecureAcls, zkSessionTimeOutMs, zkConnectionTimeoutMs,
      zkMaxInFlightRequests, time, "kafka.security", "AclAuthorizer", name=Some("ACL authorizer"),
      zkClientConfig = zkClientConfig)
    zkClient.createAclPaths()

    extendedAclSupport = kafkaConfig.interBrokerProtocolVersion >= KAFKA_2_0_IV1

    // Start change listeners first and then populate the cache so that there is no timing window
    // between loading cache and processing change notifications.
    startZkChangeListeners()
    loadCache()
  }

authorize方法

    authorize方法应该是直接调用了authorizeAction(),在这个方法里Authorizer从AuthorizableRequestContext中获取了请求的principal和host;从Action中获取了resource和operation。
鉴权流程是这样的:

  1. 判断是否超级用户,是则直接返回AuthorizationResult.ALLOWED
  2. 调用matchingAcls获取对应resource的所有权限,这里和之前的版本不一样的就是多了literal匹配和前缀匹配
  3. 判断是否配置了allow.everyone.if.no.acl.found,如果该资源没有配置任何权限,且该配置打开,则返回AuthorizationResult.ALLOWED
  4. 判断是否匹配上了DENY类的权限,若有,则直接返回AuthorizationResult.DENIED
  5. 判断是否匹配上了ALLOW类的权限,若有,则直接返回AuthorizationResult.ALLOWED
  private def authorizeAction(requestContext: AuthorizableRequestContext, action: Action): AuthorizationResult = {
    val resource = action.resourcePattern
    if (resource.patternType != PatternType.LITERAL) {
      throw new IllegalArgumentException("Only literal resources are supported. Got: " + resource.patternType)
    }

    // ensure we compare identical classes
    val sessionPrincipal = requestContext.principal
    val principal = if (classOf[KafkaPrincipal] != sessionPrincipal.getClass)
      new KafkaPrincipal(sessionPrincipal.getPrincipalType, sessionPrincipal.getName)
    else
      sessionPrincipal

    val host = requestContext.clientAddress.getHostAddress
    val operation = action.operation

    def isEmptyAclAndAuthorized(acls: Set[AclEntry]): Boolean = {
      if (acls.isEmpty) {
        // No ACLs found for this resource, permission is determined by value of config allow.everyone.if.no.acl.found
        authorizerLogger.debug(s"No acl found for resource $resource, authorized = $shouldAllowEveryoneIfNoAclIsFound")
        shouldAllowEveryoneIfNoAclIsFound
      } else false
    }

    def denyAclExists(acls: Set[AclEntry]): Boolean = {
      // Check if there are any Deny ACLs which would forbid this operation.
      matchingAclExists(operation, resource, principal, host, DENY, acls)
    }

    def allowAclExists(acls: Set[AclEntry]): Boolean = {
      // Check if there are any Allow ACLs which would allow this operation.
      // Allowing read, write, delete, or alter implies allowing describe.
      // See #{org.apache.kafka.common.acl.AclOperation} for more details about ACL inheritance.
      val allowOps = operation match {
        case DESCRIBE => Set[AclOperation](DESCRIBE, READ, WRITE, DELETE, ALTER)
        case DESCRIBE_CONFIGS => Set[AclOperation](DESCRIBE_CONFIGS, ALTER_CONFIGS)
        case _ => Set[AclOperation](operation)
      }
      allowOps.exists(operation => matchingAclExists(operation, resource, principal, host, ALLOW, acls))
    }

    def aclsAllowAccess = {
      //we allow an operation if no acls are found and user has configured to allow all users
      //when no acls are found or if no deny acls are found and at least one allow acls matches.
      val acls = matchingAcls(resource.resourceType, resource.name)
      isEmptyAclAndAuthorized(acls) || (!denyAclExists(acls) && allowAclExists(acls))
    }

    // Evaluate if operation is allowed
    val authorized = isSuperUser(principal) || aclsAllowAccess

    logAuditMessage(requestContext, action, authorized)
    if (authorized) AuthorizationResult.ALLOWED else AuthorizationResult.DENIED
  }

createAcls

    先根据传入的List<AclBinding>获取一个aclsToCreate,获取写锁之后,逐条更新Acl,并将newAcls和CurrentAcls的两个list进行合并。返回给上层。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flink是一个开源的流处理框架,而Kafka是一个分布式消息队列系统。在Flink中使用KafkaJava API可以实现将Kafka中的数据作为输入源或将处理结果输出到Kafka中。 在Flink中使用Kafka Java API的步骤通常如下: 1. 引入Kafka的依赖:首先需要将KafkaJava API的依赖添加到Flink的工程中。 2. 创建Kafka消费者:使用KafkaJava API创建一个消费者实例,可以指定消费者的一些配置如Kafka的地址、消费者组ID等。通过调用消费者的`assign()`方法或`subscribe()`方法来指定要消费的Kafka主题。 3. 创建Flink的DataStream:使用Flink的DataStream API实例化一个用于接收Kafka数据的DataStream对象。可以使用`addSource()`方法来将Kafka消费者作为数据源。可以在创建DataStream时指定Kafka消息的反序列化方式、数据类型等。 4. 执行数据处理逻辑:可以在DataStream上应用各种Flink的算子,如map、filter、reduce等,对Kafka中的数据进行处理。 5. 创建Kafka生产者:使用KafkaJava API创建一个生产者实例,可以指定生产者的一些配置。通过调用生产者的`send()`方法将处理后的结果数据发送到Kafka中。 6. 提交任务并启动Flink作业:将处理逻辑应用到Flink的任务上,并将任务提交给Flink集群进行执行。 通过以上步骤,就可以在Flink中使用KafkaJava API进行数据的输入和输出。这种方式将Kafka作为Flink的一个数据源或数据目的,使得数据可以在流处理中被实时地处理和分析。同时,由于Kafka的分布式特性,也可以保证数据的可靠性和高吞吐量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值