hadoop KerberosAuthenticationHandler 源码

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

haddop KerberosAuthenticationHandler 代码

文件路径:/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.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 org.apache.hadoop.classification.VisibleForTesting;
import org.apache.hadoop.security.authentication.client.AuthenticationException;
import org.apache.hadoop.security.authentication.client.KerberosAuthenticator;
import org.apache.commons.codec.binary.Base64;
import org.apache.hadoop.security.authentication.util.KerberosName;
import org.apache.hadoop.security.authentication.util.KerberosUtil;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.Oid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosPrincipal;
import javax.security.auth.kerberos.KeyTab;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.File;
import java.io.IOException;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Collection;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * The {@link KerberosAuthenticationHandler} implements the Kerberos SPNEGO
 * authentication mechanism for HTTP.
 * <p>
 * The supported configuration properties are:
 * <ul>
 * <li>kerberos.principal: the Kerberos principal to used by the server. As
 * stated by the Kerberos SPNEGO specification, it should be
 * <code>HTTP/${HOSTNAME}@{REALM}</code>. The realm can be omitted from the
 * principal as the JDK GSS libraries will use the realm name of the configured
 * default realm.
 * It does not have a default value.</li>
 * <li>kerberos.keytab: the keytab file containing the credentials for the
 * Kerberos principal.
 * It does not have a default value.</li>
 * <li>kerberos.name.rules: kerberos names rules to resolve principal names, see
 * {@link KerberosName#setRules(String)}</li>
 * </ul>
 */
public class KerberosAuthenticationHandler implements AuthenticationHandler {
  public static final Logger LOG = LoggerFactory.getLogger(
      KerberosAuthenticationHandler.class);

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

  /**
   * Constant for the configuration property that indicates the kerberos
   * principal.
   */
  public static final String PRINCIPAL = TYPE + ".principal";

  /**
   * Constant for the configuration property that indicates the keytab
   * file path.
   */
  public static final String KEYTAB = TYPE + ".keytab";

  /**
   * Constant for the configuration property that indicates the Kerberos name
   * rules for the Kerberos principals.
   */
  public static final String NAME_RULES = TYPE + ".name.rules";

  /**
   * Constant for the configuration property that indicates how auth_to_local
   * rules are evaluated.
   */
  public static final String RULE_MECHANISM = TYPE + ".name.rules.mechanism";

  /**
   * Constant for the list of endpoints that skips Kerberos authentication.
   */
  @VisibleForTesting
  static final String ENDPOINT_WHITELIST = TYPE + ".endpoint.whitelist";
  private static final Pattern ENDPOINT_PATTERN = Pattern.compile("^/[\\w]+");

  private String type;
  private String keytab;
  private GSSManager gssManager;
  private Subject serverSubject = new Subject();
  private final Collection<String> whitelist = new HashSet<>();

  /**
   * Creates a Kerberos SPNEGO authentication handler with the default
   * auth-token type, <code>kerberos</code>.
   */
  public KerberosAuthenticationHandler() {
    this(TYPE);
  }

  /**
   * Creates a Kerberos SPNEGO authentication handler with a custom auth-token
   * type.
   *
   * @param type auth-token type.
   */
  public KerberosAuthenticationHandler(String type) {
    this.type = type;
  }

  /**
   * Initializes the authentication handler instance.
   * <p>
   * It creates a Kerberos context using the principal and keytab specified in
   * the configuration.
   * <p>
   * This method is invoked by the {@link AuthenticationFilter#init} method.
   *
   * @param config configuration properties to initialize the handler.
   *
   * @throws ServletException thrown if the handler could not be initialized.
   */
  @Override
  public void init(Properties config) throws ServletException {
    try {
      String principal = config.getProperty(PRINCIPAL);
      if (principal == null || principal.trim().length() == 0) {
        throw new ServletException("Principal not defined in configuration");
      }
      keytab = config.getProperty(KEYTAB, keytab);
      if (keytab == null || keytab.trim().length() == 0) {
        throw new ServletException("Keytab not defined in configuration");
      }
      File keytabFile = new File(keytab);
      if (!keytabFile.exists()) {
        throw new ServletException("Keytab does not exist: " + keytab);
      }
      
      // use all SPNEGO principals in the keytab if a principal isn't
      // specifically configured
      final String[] spnegoPrincipals;
      if (principal.equals("*")) {
        spnegoPrincipals = KerberosUtil.getPrincipalNames(
            keytab, Pattern.compile("HTTP/.*"));
        if (spnegoPrincipals.length == 0) {
          throw new ServletException("Principals do not exist in the keytab");
        }
      } else {
        spnegoPrincipals = new String[]{principal};
      }
      KeyTab keytabInstance = KeyTab.getInstance(keytabFile);
      serverSubject.getPrivateCredentials().add(keytabInstance);
      for (String spnegoPrincipal : spnegoPrincipals) {
        Principal krbPrincipal = new KerberosPrincipal(spnegoPrincipal);
        LOG.info("Using keytab {}, for principal {}",
            keytab, krbPrincipal);
        serverSubject.getPrincipals().add(krbPrincipal);
      }
      String nameRules = config.getProperty(NAME_RULES, null);
      if (nameRules != null) {
        KerberosName.setRules(nameRules);
      }
      String ruleMechanism = config.getProperty(RULE_MECHANISM, null);
      if (ruleMechanism != null) {
        KerberosName.setRuleMechanism(ruleMechanism);
      }

      final String whitelistStr = config.getProperty(ENDPOINT_WHITELIST, null);
      if (whitelistStr != null) {
        final String[] strs = whitelistStr.trim().split("\\s*[,\n]\\s*");
        for (String s: strs) {
          if (s.isEmpty()) continue;
          if (ENDPOINT_PATTERN.matcher(s).matches()) {
            whitelist.add(s);
          } else {
            throw new ServletException(
                "The element of the whitelist: " + s + " must start with '/'"
                    + " and must not contain special characters afterwards");
          }
        }
      }

      try {
        gssManager = Subject.doAs(serverSubject,
            new PrivilegedExceptionAction<GSSManager>() {
              @Override
              public GSSManager run() throws Exception {
                return GSSManager.getInstance();
              }
            });
      } catch (PrivilegedActionException ex) {
        throw ex.getException();
      }
    } catch (Exception ex) {
      throw new ServletException(ex);
    }
  }

  /**
   * Releases any resources initialized by the authentication handler.
   * <p>
   * It destroys the Kerberos context.
   */
  @Override
  public void destroy() {
    keytab = null;
    serverSubject = null;
  }

  /**
   * Returns the authentication type of the authentication handler, 'kerberos'.
   * <p>
   *
   * @return the authentication type of the authentication handler, 'kerberos'.
   */
  @Override
  public String getType() {
    return type;
  }

  /**
   * Returns the Kerberos principals used by the authentication handler.
   *
   * @return the Kerberos principals used by the authentication handler.
   */
  protected Set<KerberosPrincipal> getPrincipals() {
    return serverSubject.getPrincipals(KerberosPrincipal.class);
  }

  /**
   * Returns the keytab used by the authentication handler.
   *
   * @return the keytab used by the authentication handler.
   */
  protected String getKeytab() {
    return keytab;
  }

  /**
   * This is an empty implementation, it always returns <code>TRUE</code>.
   *
   *
   *
   * @param token the authentication token if any, otherwise <code>NULL</code>.
   * @param request the HTTP client request.
   * @param response the HTTP client response.
   *
   * @return <code>TRUE</code>
   * @throws IOException it is never thrown.
   * @throws AuthenticationException it is never thrown.
   */
  @Override
  public boolean managementOperation(AuthenticationToken token,
                                     HttpServletRequest request,
                                     HttpServletResponse response)
    throws IOException, AuthenticationException {
    return true;
  }

  /**
   * It enforces the the Kerberos SPNEGO authentication sequence returning an
   * {@link AuthenticationToken} only after the Kerberos SPNEGO sequence has
   * completed successfully.
   *
   * @param request the HTTP client request.
   * @param response the HTTP client response.
   *
   * @return an authentication token if the Kerberos SPNEGO sequence is complete
   * and valid, <code>null</code> if it is in progress (in this case the handler
   * handles the response to the client).
   *
   * @throws IOException thrown if an IO error occurred.
   * @throws AuthenticationException thrown if Kerberos SPNEGO sequence failed.
   */
  @Override
  public AuthenticationToken authenticate(HttpServletRequest request,
      final HttpServletResponse response)
      throws IOException, AuthenticationException {

    // If the request servlet path is in the whitelist,
    // skip Kerberos authentication and return anonymous token.
    final String path = request.getServletPath();
    for(final String endpoint: whitelist) {
      if (endpoint.equals(path)) {
        return AuthenticationToken.ANONYMOUS;
      }
    }

    AuthenticationToken token = null;
    String authorization = request.getHeader(
        KerberosAuthenticator.AUTHORIZATION);

    if (authorization == null
        || !authorization.startsWith(KerberosAuthenticator.NEGOTIATE)) {
      response.setHeader(WWW_AUTHENTICATE, KerberosAuthenticator.NEGOTIATE);
      response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
      if (authorization == null) {
        LOG.trace("SPNEGO starting for url: {}", request.getRequestURL());
      } else {
        LOG.warn("'" + KerberosAuthenticator.AUTHORIZATION +
            "' does not start with '" +
            KerberosAuthenticator.NEGOTIATE + "' :  {}", authorization);
      }
    } else {
      authorization = authorization.substring(
          KerberosAuthenticator.NEGOTIATE.length()).trim();
      final Base64 base64 = new Base64(0);
      final byte[] clientToken = base64.decode(authorization);
      try {
        final String serverPrincipal =
            KerberosUtil.getTokenServerName(clientToken);
        if (!serverPrincipal.startsWith("HTTP/")) {
          throw new IllegalArgumentException(
              "Invalid server principal " + serverPrincipal +
              "decoded from client request");
        }
        token = Subject.doAs(serverSubject,
            new PrivilegedExceptionAction<AuthenticationToken>() {
              @Override
              public AuthenticationToken run() throws Exception {
                return runWithPrincipal(serverPrincipal, clientToken,
                      base64, response);
              }
            });
      } catch (PrivilegedActionException ex) {
        if (ex.getException() instanceof IOException) {
          throw (IOException) ex.getException();
        } else {
          throw new AuthenticationException(ex.getException());
        }
      } catch (Exception ex) {
        throw new AuthenticationException(ex);
      }
    }
    return token;
  }

  private AuthenticationToken runWithPrincipal(String serverPrincipal,
      byte[] clientToken, Base64 base64, HttpServletResponse response) throws
      IOException, GSSException {
    GSSContext gssContext = null;
    GSSCredential gssCreds = null;
    AuthenticationToken token = null;
    try {
      LOG.trace("SPNEGO initiated with server principal [{}]", serverPrincipal);
      gssCreds = this.gssManager.createCredential(
          this.gssManager.createName(serverPrincipal,
              KerberosUtil.NT_GSS_KRB5_PRINCIPAL_OID),
          GSSCredential.INDEFINITE_LIFETIME,
          new Oid[]{
              KerberosUtil.GSS_SPNEGO_MECH_OID,
              KerberosUtil.GSS_KRB5_MECH_OID },
          GSSCredential.ACCEPT_ONLY);
      gssContext = this.gssManager.createContext(gssCreds);
      byte[] serverToken = gssContext.acceptSecContext(clientToken, 0,
          clientToken.length);
      if (serverToken != null && serverToken.length > 0) {
        String authenticate = base64.encodeToString(serverToken);
        response.setHeader(KerberosAuthenticator.WWW_AUTHENTICATE,
                           KerberosAuthenticator.NEGOTIATE + " " +
                           authenticate);
      }
      if (!gssContext.isEstablished()) {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        LOG.trace("SPNEGO in progress");
      } else {
        String clientPrincipal = gssContext.getSrcName().toString();
        KerberosName kerberosName = new KerberosName(clientPrincipal);
        String userName = kerberosName.getShortName();
        token = new AuthenticationToken(userName, clientPrincipal, getType());
        response.setStatus(HttpServletResponse.SC_OK);
        LOG.trace("SPNEGO completed for client principal [{}]",
            clientPrincipal);
      }
    } finally {
      if (gssContext != null) {
        gssContext.dispose();
      }
      if (gssCreds != null) {
        gssCreds.dispose();
      }
    }
    return token;
  }
}

相关信息

hadoop 源码目录

相关文章

hadoop AltKerberosAuthenticationHandler 源码

hadoop AuthenticationFilter 源码

hadoop AuthenticationHandler 源码

hadoop AuthenticationHandlerUtil 源码

hadoop AuthenticationToken 源码

hadoop CompositeAuthenticationHandler 源码

hadoop HttpConstants 源码

hadoop JWTRedirectAuthenticationHandler 源码

hadoop LdapAuthenticationHandler 源码

hadoop MultiSchemeAuthenticationHandler 源码

0  赞