io.split.httpmodules.okhttp.HTTPKerberosAuthInterceptor Maven / Gradle / Ivy
package io.split.httpmodules.okhttp;
import java.io.IOException;
import java.util.Map;
import java.util.Date;
import java.util.Set;
import java.util.Base64;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.Principal;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.auth.Subject;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import javax.security.auth.kerberos.KerberosTicket;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Authenticator;
import okhttp3.Route;
/**
*
* An HTTP Request interceptor that modifies the request headers to enable
* Kerberos authentication. It appends the Kerberos authentication token to the
* 'Authorization' request header for Kerberos authentication
*
* Copyright 2024 MarkLogic Corporation
*
* 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.
*
*/
public class HTTPKerberosAuthInterceptor implements Authenticator {
String host;
Map krbOptions;
LoginContext loginContext;
public HTTPKerberosAuthInterceptor(String host, Map krbOptions) throws IOException {
this.host = host;
this.krbOptions = krbOptions;
try {
buildSubjectCredentials();
} catch (LoginException e) {
throw new IOException(e.getMessage(), e);
}
}
/**
* Class to create Kerberos Configuration object which specifies the Kerberos
* Login Module to be used for authentication.
*
*/
protected static class KerberosLoginConfiguration extends Configuration {
Map krbOptions = null;
public KerberosLoginConfiguration() {}
KerberosLoginConfiguration(Map krbOptions) {
this.krbOptions = krbOptions;
}
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
if (krbOptions == null) {
throw new IllegalStateException("Cannot create AppConfigurationEntry without Kerberos Options");
}
return new AppConfigurationEntry[] { new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, krbOptions) };
}
}
/**
* This method checks the validity of the TGT in the cache and build the
* Subject inside the LoginContext using Krb5LoginModule and the TGT cached by
* the Kerberos client. It assumes that a valid TGT is already present in the
* kerberos client's cache.
*
* @throws LoginException
*/
protected void buildSubjectCredentials() throws LoginException {
Subject subject = new Subject();
/**
* We are not getting the TGT from KDC here. The actual TGT is got from the
* KDC using kinit or equivalent but we use the cached TGT in order to build
* the LoginContext and populate the TGT inside the Subject using
* Krb5LoginModule
*/
LoginContext lc = getLoginContext(subject);
lc.login();
loginContext = lc;
}
protected LoginContext getLoginContext(Subject subject) throws LoginException {
return new LoginContext("Krb5LoginContext", subject, null,
(krbOptions != null) ? new KerberosLoginConfiguration(krbOptions) : new KerberosLoginConfiguration());
}
/**
* This method is responsible for getting the client principal name from the
* subject's principal set
*
* @return String the Kerberos principal name populated in the subject
* @throws IllegalStateException if there is more than 0 or more than 1
* principal is present
*/
protected String getClientPrincipalName() {
final Set principalSet = getContextSubject().getPrincipals();
if (principalSet.size() != 1)
throw new IllegalStateException(
"Only one principal is expected. Found 0 or more than one principals :" + principalSet);
return principalSet.iterator().next().getName();
}
protected Subject getContextSubject() {
Subject subject = loginContext.getSubject();
if (subject == null)
throw new IllegalStateException("Kerberos login context without subject");
return subject;
}
protected CreateAuthorizationHeaderAction getAuthorizationHeaderAction(String clientPrincipal,
String serverPrincipalName) {
return new CreateAuthorizationHeaderAction(clientPrincipal,
serverPrincipalName);
}
/**
* This method builds the Authorization header for Kerberos. It
* generates a request token based on the service ticket, client principal name and
* time-stamp
*
* @param serverPrincipalName
* the name registered with the KDC of the service for which we
* need to authenticate
* @return the HTTP Authorization header token
*/
protected String buildAuthorizationHeader(String serverPrincipalName) throws LoginException, PrivilegedActionException {
/*
* Get the principal from the Subject's private credentials and populate the
* client and server principal name for the GSS API
*/
final String clientPrincipal = getClientPrincipalName();
final CreateAuthorizationHeaderAction action = getAuthorizationHeaderAction(clientPrincipal,
serverPrincipalName);
/*
* Check if the TGT in the Subject's private credentials are valid. If
* valid, then we use the TGT in the Subject's private credentials. If not,
* we build the Subject's private credentials again from valid TGT in the
* Kerberos client cache.
*/
Set
© 2015 - 2025 Weber Informatics LLC | Privacy Policy