Java tutorial
/* * Copyright 2015 the original author or authors. * * 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. */ package org.springframework.security.kerberos.client; import java.io.IOException; import java.net.URI; import java.security.Principal; import java.security.PrivilegedAction; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; 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 javax.security.auth.login.LoginException; import org.apache.http.auth.AuthSchemeProvider; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; import org.apache.http.client.HttpClient; import org.apache.http.client.config.AuthSchemes; 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.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.springframework.http.HttpMethod; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.util.StringUtils; import org.springframework.web.client.RequestCallback; import org.springframework.web.client.ResponseExtractor; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; /** * {@code RestTemplate} that is able to make kerberos SPNEGO authenticated REST * requests. Under a hood this {@code KerberosRestTemplate} is using {@link HttpClient} to * support Kerberos. * * <p>Generally this template can be configured in few different ways. * <ul> * <li>Leave keyTabLocation and userPrincipal empty if you want to use cached ticket</li> * <li>Use keyTabLocation and userPrincipal if you want to use keytab file</li> * <li>Use userPrincipal and password if you want to use user/password</li> * <li>Use loginOptions if you want to customise Krb5LoginModule options</li> * <li>Use a customised httpClient</li> * </ul> * * @author Janne Valkealahti * */ public class KerberosRestTemplate extends RestTemplate { private static final Credentials credentials = new NullCredentials(); private final String keyTabLocation; private final String userPrincipal; private final String password; private final Map<String, Object> loginOptions; /** * Instantiates a new kerberos rest template. */ public KerberosRestTemplate() { this(null, null, null, null, buildHttpClient()); } /** * Instantiates a new kerberos rest template. * * @param httpClient the http client */ public KerberosRestTemplate(HttpClient httpClient) { this(null, null, null, null, httpClient); } /** * Instantiates a new kerberos rest template. * * @param keyTabLocation the key tab location * @param userPrincipal the user principal */ public KerberosRestTemplate(String keyTabLocation, String userPrincipal) { this(keyTabLocation, userPrincipal, buildHttpClient()); } /** * Instantiates a new kerberos rest template. * * @param keyTabLocation the key tab location * @param userPrincipal the user principal * @param httpClient the http client */ public KerberosRestTemplate(String keyTabLocation, String userPrincipal, HttpClient httpClient) { this(keyTabLocation, userPrincipal, null, null, httpClient); } /** * Instantiates a new kerberos rest template. * * @param loginOptions the login options */ public KerberosRestTemplate(Map<String, Object> loginOptions) { this(null, null, null, loginOptions, buildHttpClient()); } /** * Instantiates a new kerberos rest template. * * @param loginOptions the login options * @param httpClient the http client */ public KerberosRestTemplate(Map<String, Object> loginOptions, HttpClient httpClient) { this(null, null, null, loginOptions, httpClient); } /** * Instantiates a new kerberos rest template. * * @param keyTabLocation the key tab location * @param userPrincipal the user principal * @param loginOptions the login options */ public KerberosRestTemplate(String keyTabLocation, String userPrincipal, Map<String, Object> loginOptions) { this(keyTabLocation, userPrincipal, null, loginOptions, buildHttpClient()); } /** * Instantiates a new kerberos rest template. * * @param keyTabLocation the key tab location * @param userPrincipal the user principal * @param password the password * @param loginOptions the login options */ public KerberosRestTemplate(String keyTabLocation, String userPrincipal, String password, Map<String, Object> loginOptions) { this(keyTabLocation, userPrincipal, password, loginOptions, buildHttpClient()); } /** * Instantiates a new kerberos rest template. * * @param keyTabLocation the key tab location * @param userPrincipal the user principal * @param loginOptions the login options * @param httpClient the http client */ private KerberosRestTemplate(String keyTabLocation, String userPrincipal, Map<String, Object> loginOptions, HttpClient httpClient) { this(keyTabLocation, userPrincipal, null, loginOptions, httpClient); } /** * Instantiates a new kerberos rest template. * * @param keyTabLocation the key tab location * @param userPrincipal the user principal * @param password the password * @param loginOptions the login options * @param httpClient the http client */ private KerberosRestTemplate(String keyTabLocation, String userPrincipal, String password, Map<String, Object> loginOptions, HttpClient httpClient) { super(new HttpComponentsClientHttpRequestFactory(httpClient)); this.keyTabLocation = keyTabLocation; this.userPrincipal = userPrincipal; this.password = password; this.loginOptions = loginOptions; } /** * Builds the default instance of {@link HttpClient} having kerberos * support. * * @return the http client with spneno auth scheme */ private static HttpClient buildHttpClient() { 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), credentials); builder.setDefaultCredentialsProvider(credentialsProvider); CloseableHttpClient httpClient = builder.build(); return httpClient; } /** * Setup the {@link LoginContext} with credentials and options for authentication against kerberos. * * @return the login context */ private LoginContext buildLoginContext() throws LoginException { ClientLoginConfig loginConfig = new ClientLoginConfig(keyTabLocation, userPrincipal, password, loginOptions); Set<Principal> princ = new HashSet<Principal>(1); princ.add(new KerberosPrincipal(userPrincipal)); Subject sub = new Subject(false, princ, new HashSet<Object>(), new HashSet<Object>()); CallbackHandler callbackHandler = new CallbackHandlerImpl(userPrincipal, password); LoginContext lc = new LoginContext("", sub, callbackHandler, loginConfig); return lc; } @Override protected final <T> T doExecute(final URI url, final HttpMethod method, final RequestCallback requestCallback, final ResponseExtractor<T> responseExtractor) throws RestClientException { try { LoginContext lc = buildLoginContext(); lc.login(); Subject serviceSubject = lc.getSubject(); return Subject.doAs(serviceSubject, new PrivilegedAction<T>() { @Override public T run() { return KerberosRestTemplate.this.doExecuteSubject(url, method, requestCallback, responseExtractor); } }); } catch (Exception e) { throw new RestClientException("Error running rest call", e); } } private <T> T doExecuteSubject(URI url, HttpMethod method, RequestCallback requestCallback, ResponseExtractor<T> responseExtractor) throws RestClientException { return super.doExecute(url, method, requestCallback, responseExtractor); } private static class ClientLoginConfig extends Configuration { private final String keyTabLocation; private final String userPrincipal; private final String password; private final Map<String, Object> loginOptions; private ClientLoginConfig(String keyTabLocation, String userPrincipal, String password, Map<String, Object> loginOptions) { super(); this.keyTabLocation = keyTabLocation; this.userPrincipal = userPrincipal; this.password = password; this.loginOptions = loginOptions; } @Override public AppConfigurationEntry[] getAppConfigurationEntry(String name) { Map<String, Object> options = new HashMap<String, Object>(); // if we don't have keytab or principal only option is to rely on // credentials cache. if (!StringUtils.hasText(keyTabLocation) || !StringUtils.hasText(userPrincipal)) { // cache options.put("useTicketCache", "true"); } else { // keytab options.put("useKeyTab", "true"); options.put("keyTab", this.keyTabLocation); options.put("principal", this.userPrincipal); options.put("storeKey", "true"); } options.put("doNotPrompt", Boolean.toString(password == null)); options.put("isInitiator", "true"); if (loginOptions != null) { options.putAll(loginOptions); } return new AppConfigurationEntry[] { new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options) }; } } private static class NullCredentials implements Credentials { @Override public Principal getUserPrincipal() { return null; } @Override public String getPassword() { return null; } } private static class CallbackHandlerImpl implements CallbackHandler { private final String userPrincipal; private final String password; private CallbackHandlerImpl(String userPrincipal, String password) { super(); this.userPrincipal = userPrincipal; this.password = password; } @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (Callback callback : callbacks) { if (callback instanceof NameCallback) { NameCallback nc = (NameCallback) callback; nc.setName(userPrincipal); } else if (callback instanceof PasswordCallback) { PasswordCallback pc = (PasswordCallback) callback; pc.setPassword(password.toCharArray()); } else { throw new UnsupportedCallbackException(callback, "Unknown Callback"); } } } } }