Kerberos connection using HTTP Client

18,215

Solution 1

SPNEGO will not work because you use localhost as URL hostname.

Your server is configured for a set of SPNs (or at least one) beginning with HTTP/ registered on the ActiveDirectory service account. You can query them from AD thanks to setspn -l yourServiceAccount.

Your URL must use an effective server hostname known as SPN in ActiveDirectory so that Apache Http Client can negotiate a TGS for this service and send it to your server.

Solution 2

Here is a test client I wrote in my project. This client relies on all encryption types to be enabled on JDK,

If you see following in your logs and your keytab is encrypted at 256 bit default etypes for default_tkt_enctypes: 17 16 23 1 3.

then following jar http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html needs to be downloaded and placed in JDK/jre/lib/security to enable AES256 bit encryption after that you should see following in logs default etypes for default_tkt_enctypes: 18 17 16 23 1 3.

import java.io.IOException;
import java.io.InputStream;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosPrincipal;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthSchemeProvider;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.config.Lookup;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.impl.auth.SPNegoSchemeFactory;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.cookie.BasicClientCookie;

Utility class

public class KerberosHttpClient {
    private String principal;
    private String keyTabLocation;

    public KerberosHttpClient() {}
    public KerberosHttpClient(String principal, String keyTabLocation) {
        super();
        this.principal = principal;
        this.keyTabLocation = keyTabLocation;
    }

    public KerberosHttpClient(String principal, String keyTabLocation, String krb5Location) {
        this(principal, keyTabLocation);
        System.setProperty("java.security.krb5.conf", krb5Location);
    }

    public KerberosHttpClient(String principal, String keyTabLocation, boolean isDebug) {
        this(principal, keyTabLocation);
        if (isDebug) {
            System.setProperty("sun.security.spnego.debug", "true");
            System.setProperty("sun.security.krb5.debug", "true");
        }
    }

    public KerberosHttpClient(String principal, String keyTabLocation, String krb5Location, boolean isDebug) {
        this(principal, keyTabLocation, isDebug);        
        System.setProperty("java.security.krb5.conf", krb5Location);
    }

    private static HttpClient buildSpengoHttpClient() {
        HttpClientBuilder builder = HttpClientBuilder.create();            
        Lookup<AuthSchemeProvider> authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create().
                register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(true)).build();
        builder.setDefaultAuthSchemeRegistry(authSchemeRegistry);
        BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(new AuthScope(null, -1, null), new Credentials() {
            @Override
            public Principal getUserPrincipal() {
                return null;
            }
            @Override
            public String getPassword() {
                return null;
            }
        });
        builder.setDefaultCredentialsProvider(credentialsProvider);        
        CloseableHttpClient httpClient = builder.build();
        return httpClient;
    }

    public HttpResponse callRestUrl(final String url,final String userId) {
        //keyTabLocation = keyTabLocation.substring("file://".length());
        System.out.println(String.format("Calling KerberosHttpClient %s %s %s",this.principal, this.keyTabLocation, url));
        Configuration config = new Configuration() {
            @SuppressWarnings("serial")
            @Override
            public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
                return new AppConfigurationEntry[] { new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
                        AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, new HashMap<String, Object>() {
                            {
                                put("useTicketCache", "false");
                                put("useKeyTab", "true");
                                put("keyTab", keyTabLocation);
                                //Krb5 in GSS API needs to be refreshed so it does not throw the error
                                //Specified version of key is not available
                                put("refreshKrb5Config", "true");
                                put("principal", principal);
                                put("storeKey", "true");
                                put("doNotPrompt", "true");
                                put("isInitiator", "true");
                                put("debug", "true");
                            }
                        }) };
            }
        };
        Set<Principal> princ = new HashSet<Principal>(1);
        princ.add(new KerberosPrincipal(userId));
        Subject sub = new Subject(false, princ, new HashSet<Object>(), new HashSet<Object>());
        try {
            LoginContext lc = new LoginContext("", sub, null, config);
            lc.login();
            Subject serviceSubject = lc.getSubject();
            return Subject.doAs(serviceSubject, new PrivilegedAction<HttpResponse>() {
                HttpResponse httpResponse = null;
                @Override
                public HttpResponse run() {
                    try {    
                        HttpUriRequest request = new HttpGet(url);
                        HttpClient spnegoHttpClient = buildSpengoHttpClient();
                        httpResponse = spnegoHttpClient.execute(request);
                                                return httpResponse;
                    } catch (IOException ioe) {
                        ioe.printStackTrace();
                    }
                    return httpResponse;
                }
            });
        } catch (Exception le) {
            le.printStackTrace();;
        }
        return null;
    }

    public static void main(String[] args) throws UnsupportedOperationException, IOException {
        KerberosHttpClient restTest = new KerberosHttpClient("HTTP/[email protected]",
                "file://C://Development//test.keytab", true);
        HttpResponse response = restTest.callRestUrl("http://test.com/service/employees",
                "HTTP/[email protected]");
        InputStream is = response.getEntity().getContent();
        System.out.println("Status code " + response.getStatusLine().getStatusCode());
        System.out.println(Arrays.deepToString(response.getAllHeaders()));
        System.out.println(new String(IOUtils.toByteArray(is), "UTF-8"));
    }
}
Share:
18,215
Admin
Author by

Admin

Updated on July 12, 2022

Comments

  • Admin
    Admin almost 2 years

    I'm writing HTTP connection with Kerberos authentication. I have "HTTP/1.1 401 Unauthorized". Could you recommend me what I should check? I think there's somethink trick, but I don't see it.

    May be I should set header "WWW-Authenticate" with "Negotiate"?

    Thank a lot in advanced for any help and ideas.

    public class ClientKerberosAuthentication {
    
        public static void main(String[] args) throws Exception {
    
            System.setProperty("java.security.auth.login.config", "login.conf");
            System.setProperty("java.security.krb5.conf", "krb5.conf");
            System.setProperty("sun.security.krb5.debug", "true");
            System.setProperty("javax.security.auth.useSubjectCredsOnly","false");
    
            DefaultHttpClient httpclient = new DefaultHttpClient();
            try {
               NegotiateSchemeFactory nsf = new NegotiateSchemeFactory();
               httpclient.getAuthSchemes().register(AuthPolicy.SPNEGO, nsf);            
    
               List<String> authpref = new ArrayList<String>();
               authpref.add(AuthPolicy.BASIC);
               authpref.add(AuthPolicy.SPNEGO);
               httpclient.getParams().setParameter(AuthPNames.PROXY_AUTH_PREF, authpref);            
    
    
               httpclient.getCredentialsProvider().setCredentials(
                      new AuthScope(null, -1, AuthScope.ANY_REALM, AuthPolicy.SPNEGO), 
                      new UsernamePasswordCredentials("myuser", "mypass"));            
    
               System.out.println("----------------------------------------");
               HttpUriRequest request = new HttpGet("http://localhost:8084/web-app/webdav/213/_test.docx");
               HttpResponse response = httpclient.execute(request);
               HttpEntity entity = response.getEntity();
    
               System.out.println("----------------------------------------");
               System.out.println(response.getStatusLine());
               System.out.println("----------------------------------------");
               if (entity != null) {
                   System.out.println(EntityUtils.toString(entity));
               }
               System.out.println("----------------------------------------");
    
               // This ensures the connection gets released back to the manager
               EntityUtils.consume(entity);
    
            } finally {
               httpclient.getConnectionManager().shutdown();
            }
        }
    }