Livy自定义安全认证

46 阅读1分钟

大多数场景下,大数据环境会使用Kerberos进行安全加固。正如配置文件livy.conf.template所描述的Livy支持的认证方式有Kerberos和自定义认证。

# Authentication support for Livy server
# Livy has a built-in SPnego authentication support for HTTP requests  with below configurations.
# livy.server.auth.type = kerberos
# livy.server.auth.kerberos.principal = <spnego principal>
# livy.server.auth.kerberos.keytab = <spnego keytab>
# livy.server.auth.kerberos.name-rules = DEFAULT
#
# If user wants to use custom authentication filter, configurations are:
# livy.server.auth.type = <custom>
# livy.server.auth.<custom>.class = <class of custom auth filter>
# livy.server.auth.<custom>.param.<foo1> = <bar1>
# livy.server.auth.<custom>.param.<foo2> = <bar2>

本博客将简单介绍如何给Livy实现一个使用用户名和密码的认证方式。

1 源码实现逻辑分析

1.1 认证入口

org.apache.livy.server.LivyServer

livyConf.get(AUTH_TYPE) match {
**** 中间省略
      case customType =>
        val authClassConf = s"livy.server.auth.$customType.class"
        val authClass = livyConf.get(authClassConf)
        require(authClass != null, s"$customType auth requires $authClassConf to be provided")

        val holder = new FilterHolder()
        holder.setClassName(authClass)

        val prefix = s"livy.server.auth.$customType.param."
        livyConf.asScala.filter { kv =>
          kv.getKey.length > prefix.length && kv.getKey.startsWith(prefix)
        }.foreach { kv => holder.setInitParameter(kv.getKey.substring(prefix.length), kv.getValue)        }
        server.context.addFilter(holder, "/*", EnumSet.allOf(classOf[DispatcherType]))
        info(s"$customType auth enabled")

需要关注里面用到的3个配置

  • customType 对应配置livy.server.auth.type
  • livy.server.auth.$customType.class 我们使用的Filter
  • livy.server.auth.$customType.param. 可定义多个参数

1.2 class选择和param传递

和kerberos认证一样,选择org.apache.hadoop.security.authentication.server

val holder = new FilterHolder(new AuthenticationFilter())

里面比较核心的一段代码,AuthenticationFilter对象调用authHandler中的authenticate方法。

public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
...      
if (authHandler.managementOperation(token, httpRequest, httpResponse)) {
        if (token == null) {
          if (LOG.isDebugEnabled()) {
            LOG.debug("Request [{}] triggering authentication", getRequestURL(httpRequest));
          }
          token = authHandler.authenticate(httpRequest, httpResponse);
          if (token != null && token.getExpires() != 0 &&
              token != AuthenticationToken.ANONYMOUS) {
            token.setExpires(System.currentTimeMillis() + getValidity() * 1000);
          }
          newToken = true;
        }

authHandler在此处创建,其中AUTH_TYPE=type

public void init(FilterConfig filterConfig) throws ServletException {
  String configPrefix = filterConfig.getInitParameter(CONFIG_PREFIX);
  configPrefix = (configPrefix != null) ? configPrefix + "." : "";
  config = getConfiguration(configPrefix, filterConfig);
  String authHandlerName = config.getProperty(AUTH_TYPE, null);
  String authHandlerClassName;
  if (authHandlerName == null) {
    throw new ServletException("Authentication type must be specified: " +
        PseudoAuthenticationHandler.TYPE + "|" + 
        KerberosAuthenticationHandler.TYPE + "|<class>");
  }
  if (authHandlerName.toLowerCase(Locale.ENGLISH).equals(
      PseudoAuthenticationHandler.TYPE)) {
    authHandlerClassName = PseudoAuthenticationHandler.class.getName();
  } else if (authHandlerName.toLowerCase(Locale.ENGLISH).equals(
      KerberosAuthenticationHandler.TYPE)) {
    authHandlerClassName = KerberosAuthenticationHandler.class.getName();
  } else {
    authHandlerClassName = authHandlerName;
  }

参数传递逻辑如下,其中也包含上面会用到的type

val prefix = s"livy.server.auth.$customType.param."
livyConf.asScala.filter { kv =>
  kv.getKey.length > prefix.length && kv.getKey.startsWith(prefix)
}.foreach { kv =>
  holder.setInitParameter(kv.getKey.substring(prefix.length), kv.getValue)
}
server.context.addFilter(holder, "/*", EnumSet.allOf(classOf[DispatcherType]))

2 认证开发和配置

通过上面的逻辑分析,我们知道需要开发的内容是type对应的class(需要继承AuthenticationHandler),并且可以在param定义class所需的各种参数。

/**
 * @author xxx
 * @date 2023/3/9 5:33 PM
 */
class LivyUserAuth extends AuthenticationHandler {

  val log = LoggerFactory.getLogger(this.getClass)

  private var userPassList:mutable.Map[String, String] = mutable.HashMap()
  private var hdfsLocation:String = null
  private var fileLocation:String = null
  private var periodSecond:Int = -1
  private var decryptType:String = null
  private val CACHE_SIZE:Int = 1000

  private var passwordSupplier:Supplier[PasswordStore] = null

  override def getType: String = "xxx"

  override def init(properties: Properties): Unit = {
  }

  override def destroy(): Unit = {

  }

  /**
   * return true when the request processing should continue. If false is returned, it means the response has been populated by this method
   * @param authenticationToken
   * @param httpServletRequest
   * @param httpServletResponse
   */
  @throws[IOException]
  @throws[AuthenticationException]
  override def managementOperation(authenticationToken: AuthenticationToken,
                                   httpServletRequest: HttpServletRequest,
                                   httpServletResponse: HttpServletResponse): Boolean = {
    return true
  }

  @throws[IOException]
  @throws[AuthenticationException]
  override def authenticate(request: HttpServletRequest,
                            response: HttpServletResponse): AuthenticationToken = {

    var token: AuthenticationToken = null
    var authorization = request.getHeader("Authorization")
    log.info("authorization:" + authorization)
    token
  }
}

livy.conf新增以下配置

livy.server.auth.type nxb
livy.server.auth.nxb.class org.apache.hadoop.security.authentication.server.AuthenticationFilter
livy.server.auth.nxb.param.file.location xxxxxxxxxx
livy.server.auth.nxb.param.period.second 1800
livy.server.auth.nxb.param.type com.nxb.livy.auth.LivyUserAuth

3 打包和部署

只需要将项目打包即可,依赖不需要(Livy Jars里面已经有了)

 <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <outputDirectory>${project.build.directory}/jars</outputDirectory>
                </configuration>
            </plugin>
        </plugins>

将打包好jar放在/usr/lib/livy/jars/

重启服务

  • sudo systemctl restart livy-server.service