hadoop LdapAuthenticationHandler 源码

  • 2022-10-20
  • 浏览 (431)

haddop LdapAuthenticationHandler 代码

文件路径:/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/LdapAuthenticationHandler.java

/**
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. See accompanying LICENSE file.
 */
package org.apache.hadoop.security.authentication.server;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Hashtable;
import java.util.Properties;

import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.InitialDirContext;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.StartTlsRequest;
import javax.naming.ldap.StartTlsResponse;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.codec.binary.Base64;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.security.authentication.client.AuthenticationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.hadoop.classification.VisibleForTesting;

/**
 * The {@link LdapAuthenticationHandler} implements the BASIC authentication
 * mechanism for HTTP using LDAP back-end.
 *
 * The supported configuration properties are:
 * <ul>
 * <li>ldap.providerurl: The url of the LDAP server. It does not have a default
 * value.</li>
 * <li>ldap.basedn: the base distinguished name (DN) to be used with the LDAP
 * server. This value is appended to the provided user id for authentication
 * purpose. It does not have a default value.</li>
 * <li>ldap.binddomain: the LDAP bind domain value to be used with the LDAP
 * server. This property is optional and useful only in case of Active
 * Directory server.
 * <li>ldap.enablestarttls: A boolean value used to define if the LDAP server
 * supports 'StartTLS' extension.</li>
 * </ul>
 */
@InterfaceAudience.Private
@InterfaceStability.Evolving
public class LdapAuthenticationHandler implements AuthenticationHandler {
  private static Logger logger = LoggerFactory
      .getLogger(LdapAuthenticationHandler.class);

  /**
   * Constant that identifies the authentication mechanism.
   */
  public static final String TYPE = "ldap";

  /**
   * Constant that identifies the authentication mechanism to be used with the
   * LDAP server.
   */
  public static final String SECURITY_AUTHENTICATION = "simple";

  /**
   * Constant for the configuration property that indicates the url of the LDAP
   * server.
   */
  public static final String PROVIDER_URL = TYPE + ".providerurl";

  /**
   * Constant for the configuration property that indicates the base
   * distinguished name (DN) to be used with the LDAP server. This value is
   * appended to the provided user id for authentication purpose.
   */
  public static final String BASE_DN = TYPE + ".basedn";

  /**
   * Constant for the configuration property that indicates the LDAP bind
   * domain value to be used with the LDAP server.
   */
  public static final String LDAP_BIND_DOMAIN = TYPE + ".binddomain";

  /**
   * Constant for the configuration property that indicates whether
   * the LDAP server supports 'StartTLS' extension.
   */
  public static final String ENABLE_START_TLS = TYPE + ".enablestarttls";

  private String ldapDomain;
  private String baseDN;
  private String providerUrl;
  private Boolean enableStartTls;
  private Boolean disableHostNameVerification;

  /**
   * Configure StartTLS LDAP extension for this handler.
   *
   * @param enableStartTls true If the StartTLS LDAP extension is to be enabled
   *          false otherwise
   */
  @VisibleForTesting
  public void setEnableStartTls(Boolean enableStartTls) {
    this.enableStartTls = enableStartTls;
  }

  /**
   * Configure the Host name verification for this handler. This method is
   * introduced only for unit testing and should never be used in production.
   *
   * @param disableHostNameVerification true to disable host-name verification
   *          false otherwise
   */
  @VisibleForTesting
  public void setDisableHostNameVerification(
      Boolean disableHostNameVerification) {
    this.disableHostNameVerification = disableHostNameVerification;
  }

  @Override
  public String getType() {
    return TYPE;
  }

  @Override
  public void init(Properties config) throws ServletException {
    this.baseDN = config.getProperty(BASE_DN);
    this.providerUrl = config.getProperty(PROVIDER_URL);
    this.ldapDomain = config.getProperty(LDAP_BIND_DOMAIN);
    this.enableStartTls =
        Boolean.valueOf(config.getProperty(ENABLE_START_TLS, "false"));

    if (this.providerUrl == null) {
      throw new NullPointerException("The LDAP URI can not be null");
    }
    if (!((this.baseDN == null)
        ^ (this.ldapDomain == null))) {
      throw new IllegalArgumentException(
          "Either LDAP base DN or LDAP domain value needs to be specified");
    }
    if (this.enableStartTls) {
      String tmp = this.providerUrl.toLowerCase();
      if (tmp.startsWith("ldaps")) {
        throw new IllegalArgumentException(
            "Can not use ldaps and StartTLS option at the same time");
      }
    }
  }

  @Override
  public void destroy() {
  }

  @Override
  public boolean managementOperation(AuthenticationToken token,
      HttpServletRequest request, HttpServletResponse response)
      throws IOException, AuthenticationException {
    return true;
  }

  @Override
  public AuthenticationToken authenticate(HttpServletRequest request,
      HttpServletResponse response)
          throws IOException, AuthenticationException {
    AuthenticationToken token = null;
    String authorization =
        request.getHeader(HttpConstants.AUTHORIZATION_HEADER);

    if (authorization == null
        || !AuthenticationHandlerUtil.matchAuthScheme(HttpConstants.BASIC,
            authorization)) {
      response.setHeader(WWW_AUTHENTICATE, HttpConstants.BASIC);
      response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
      if (authorization == null) {
        logger.trace("Basic auth starting");
      } else {
        logger.warn("'" + HttpConstants.AUTHORIZATION_HEADER
            + "' does not start with '" + HttpConstants.BASIC + "' :  {}",
            authorization);
      }
    } else {
      authorization =
          authorization.substring(HttpConstants.BASIC.length()).trim();
      final Base64 base64 = new Base64(0);
      // As per RFC7617, UTF-8 charset should be used for decoding.
      String[] credentials = new String(base64.decode(authorization),
          StandardCharsets.UTF_8).split(":", 2);
      if (credentials.length == 2) {
        token = authenticateUser(credentials[0], credentials[1]);
        response.setStatus(HttpServletResponse.SC_OK);
      }
    }
    return token;
  }

  private AuthenticationToken authenticateUser(String userName,
      String password) throws AuthenticationException {
    if (userName == null || userName.isEmpty()) {
      throw new AuthenticationException("Error validating LDAP user:"
          + " a null or blank username has been provided");
    }

    // If the domain is available in the config, then append it unless domain
    // is already part of the username. LDAP providers like Active Directory
    // use a fully qualified user name like foo@bar.com.
    if (!hasDomain(userName) && ldapDomain != null) {
      userName = userName + "@" + ldapDomain;
    }

    if (password == null || password.isEmpty() ||
        password.getBytes(StandardCharsets.UTF_8)[0] == 0) {
      throw new AuthenticationException("Error validating LDAP user:"
          + " a null or blank password has been provided");
    }

    // setup the security principal
    String bindDN;
    if (baseDN == null) {
      bindDN = userName;
    } else {
      bindDN = "uid=" + userName + "," + baseDN;
    }

    if (this.enableStartTls) {
      authenticateWithTlsExtension(bindDN, password);
    } else {
      authenticateWithoutTlsExtension(bindDN, password);
    }

    return new AuthenticationToken(userName, userName, TYPE);
  }

  private void authenticateWithTlsExtension(String userDN, String password)
      throws AuthenticationException {
    LdapContext ctx = null;
    Hashtable<String, Object> env = new Hashtable<String, Object>();
    env.put(Context.INITIAL_CONTEXT_FACTORY,
        "com.sun.jndi.ldap.LdapCtxFactory");
    env.put(Context.PROVIDER_URL, providerUrl);

    try {
      // Create initial context
      ctx = new InitialLdapContext(env, null);
      // Establish TLS session
      StartTlsResponse tls =
          (StartTlsResponse) ctx.extendedOperation(new StartTlsRequest());

      if (disableHostNameVerification) {
        tls.setHostnameVerifier(new HostnameVerifier() {
          @Override
          public boolean verify(String hostname, SSLSession session) {
            return true;
          }
        });
      }

      tls.negotiate();

      // Initialize security credentials & perform read operation for
      // verification.
      ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION,
          SECURITY_AUTHENTICATION);
      ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, userDN);
      ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, password);
      ctx.lookup(userDN);
      logger.debug("Authentication successful for {}", userDN);

    } catch (NamingException | IOException ex) {
      throw new AuthenticationException("Error validating LDAP user", ex);
    } finally {
      if (ctx != null) {
        try {
          ctx.close();
        } catch (NamingException e) { /* Ignore. */
        }
      }
    }
  }

  private void authenticateWithoutTlsExtension(String userDN, String password)
      throws AuthenticationException {
    Hashtable<String, Object> env = new Hashtable<String, Object>();
    env.put(Context.INITIAL_CONTEXT_FACTORY,
        "com.sun.jndi.ldap.LdapCtxFactory");
    env.put(Context.PROVIDER_URL, providerUrl);
    env.put(Context.SECURITY_AUTHENTICATION, SECURITY_AUTHENTICATION);
    env.put(Context.SECURITY_PRINCIPAL, userDN);
    env.put(Context.SECURITY_CREDENTIALS, password);

    try {
      // Create initial context
      Context ctx = new InitialDirContext(env);
      ctx.close();
      logger.debug("Authentication successful for {}", userDN);

    } catch (NamingException e) {
      throw new AuthenticationException("Error validating LDAP user", e);
    }
  }

  private static boolean hasDomain(String userName) {
    return (indexOfDomainMatch(userName) > 0);
  }

  /*
   * Get the index separating the user name from domain name (the user's name
   * up to the first '/' or '@').
   *
   * @param userName full user name.
   *
   * @return index of domain match or -1 if not found
   */
  private static int indexOfDomainMatch(String userName) {
    if (userName == null) {
      return -1;
    }

    int idx = userName.indexOf('/');
    int idx2 = userName.indexOf('@');
    int endIdx = Math.min(idx, idx2); // Use the earlier match.
    // Unless at least one of '/' or '@' was not found, in
    // which case, user the latter match.
    if (endIdx == -1) {
      endIdx = Math.max(idx, idx2);
    }
    return endIdx;
  }

}

相关信息

hadoop 源码目录

相关文章

hadoop AltKerberosAuthenticationHandler 源码

hadoop AuthenticationFilter 源码

hadoop AuthenticationHandler 源码

hadoop AuthenticationHandlerUtil 源码

hadoop AuthenticationToken 源码

hadoop CompositeAuthenticationHandler 源码

hadoop HttpConstants 源码

hadoop JWTRedirectAuthenticationHandler 源码

hadoop KerberosAuthenticationHandler 源码

hadoop MultiSchemeAuthenticationHandler 源码

0  赞