| /** |
| * 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.client; |
| |
| import org.apache.hadoop.security.authentication.server.AuthenticationFilter; |
| |
| import java.io.IOException; |
| import java.net.HttpURLConnection; |
| import java.net.URL; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * The {@link AuthenticatedURL} class enables the use of the JDK {@link URL} class |
| * against HTTP endpoints protected with the {@link AuthenticationFilter}. |
| * <p/> |
| * The authentication mechanisms supported by default are Hadoop Simple authentication |
| * (also known as pseudo authentication) and Kerberos SPNEGO authentication. |
| * <p/> |
| * Additional authentication mechanisms can be supported via {@link Authenticator} implementations. |
| * <p/> |
| * The default {@link Authenticator} is the {@link KerberosAuthenticator} class which supports |
| * automatic fallback from Kerberos SPNEGO to Hadoop Simple authentication. |
| * <p/> |
| * <code>AuthenticatedURL</code> instances are not thread-safe. |
| * <p/> |
| * The usage pattern of the {@link AuthenticatedURL} is: |
| * <p/> |
| * <pre> |
| * |
| * // establishing an initial connection |
| * |
| * URL url = new URL("http://foo:8080/bar"); |
| * AuthenticatedURL.Token token = new AuthenticatedURL.Token(); |
| * AuthenticatedURL aUrl = new AuthenticatedURL(); |
| * HttpURLConnection conn = new AuthenticatedURL(url, token).openConnection(); |
| * .... |
| * // use the 'conn' instance |
| * .... |
| * |
| * // establishing a follow up connection using a token from the previous connection |
| * |
| * HttpURLConnection conn = new AuthenticatedURL(url, token).openConnection(); |
| * .... |
| * // use the 'conn' instance |
| * .... |
| * |
| * </pre> |
| */ |
| public class AuthenticatedURL { |
| |
| /** |
| * Name of the HTTP cookie used for the authentication token between the client and the server. |
| */ |
| public static final String AUTH_COOKIE = "hadoop.auth"; |
| |
| private static final String AUTH_COOKIE_EQ = AUTH_COOKIE + "="; |
| |
| /** |
| * Client side authentication token. |
| */ |
| public static class Token { |
| |
| private String token; |
| |
| /** |
| * Creates a token. |
| */ |
| public Token() { |
| } |
| |
| /** |
| * Creates a token using an existing string representation of the token. |
| * |
| * @param tokenStr string representation of the tokenStr. |
| */ |
| public Token(String tokenStr) { |
| if (tokenStr == null) { |
| throw new IllegalArgumentException("tokenStr cannot be null"); |
| } |
| set(tokenStr); |
| } |
| |
| /** |
| * Returns if a token from the server has been set. |
| * |
| * @return if a token from the server has been set. |
| */ |
| public boolean isSet() { |
| return token != null; |
| } |
| |
| /** |
| * Sets a token. |
| * |
| * @param tokenStr string representation of the tokenStr. |
| */ |
| void set(String tokenStr) { |
| token = tokenStr; |
| } |
| |
| /** |
| * Returns the string representation of the token. |
| * |
| * @return the string representation of the token. |
| */ |
| @Override |
| public String toString() { |
| return token; |
| } |
| |
| /** |
| * Return the hashcode for the token. |
| * |
| * @return the hashcode for the token. |
| */ |
| @Override |
| public int hashCode() { |
| return (token != null) ? token.hashCode() : 0; |
| } |
| |
| /** |
| * Return if two token instances are equal. |
| * |
| * @param o the other token instance. |
| * |
| * @return if this instance and the other instance are equal. |
| */ |
| @Override |
| public boolean equals(Object o) { |
| boolean eq = false; |
| if (o instanceof Token) { |
| Token other = (Token) o; |
| eq = (token == null && other.token == null) || (token != null && this.token.equals(other.token)); |
| } |
| return eq; |
| } |
| } |
| |
| private static Class<? extends Authenticator> DEFAULT_AUTHENTICATOR = KerberosAuthenticator.class; |
| |
| /** |
| * Sets the default {@link Authenticator} class to use when an {@link AuthenticatedURL} instance |
| * is created without specifying an authenticator. |
| * |
| * @param authenticator the authenticator class to use as default. |
| */ |
| public static void setDefaultAuthenticator(Class<? extends Authenticator> authenticator) { |
| DEFAULT_AUTHENTICATOR = authenticator; |
| } |
| |
| /** |
| * Returns the default {@link Authenticator} class to use when an {@link AuthenticatedURL} instance |
| * is created without specifying an authenticator. |
| * |
| * @return the authenticator class to use as default. |
| */ |
| public static Class<? extends Authenticator> getDefaultAuthenticator() { |
| return DEFAULT_AUTHENTICATOR; |
| } |
| |
| private Authenticator authenticator; |
| private ConnectionConfigurator connConfigurator; |
| |
| /** |
| * Creates an {@link AuthenticatedURL}. |
| */ |
| public AuthenticatedURL() { |
| this(null); |
| } |
| |
| /** |
| * Creates an <code>AuthenticatedURL</code>. |
| * |
| * @param authenticator the {@link Authenticator} instance to use, if <code>null</code> a {@link |
| * KerberosAuthenticator} is used. |
| */ |
| public AuthenticatedURL(Authenticator authenticator) { |
| this(authenticator, null); |
| } |
| |
| /** |
| * Creates an <code>AuthenticatedURL</code>. |
| * |
| * @param authenticator the {@link Authenticator} instance to use, if <code>null</code> a {@link |
| * KerberosAuthenticator} is used. |
| * @param connConfigurator a connection configurator. |
| */ |
| public AuthenticatedURL(Authenticator authenticator, |
| ConnectionConfigurator connConfigurator) { |
| try { |
| this.authenticator = (authenticator != null) ? authenticator : DEFAULT_AUTHENTICATOR.newInstance(); |
| } catch (Exception ex) { |
| throw new RuntimeException(ex); |
| } |
| this.connConfigurator = connConfigurator; |
| this.authenticator.setConnectionConfigurator(connConfigurator); |
| } |
| |
| /** |
| * Returns an authenticated {@link HttpURLConnection}. |
| * |
| * @param url the URL to connect to. Only HTTP/S URLs are supported. |
| * @param token the authentication token being used for the user. |
| * |
| * @return an authenticated {@link HttpURLConnection}. |
| * |
| * @throws IOException if an IO error occurred. |
| * @throws AuthenticationException if an authentication exception occurred. |
| */ |
| public HttpURLConnection openConnection(URL url, Token token) throws IOException, AuthenticationException { |
| if (url == null) { |
| throw new IllegalArgumentException("url cannot be NULL"); |
| } |
| if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) { |
| throw new IllegalArgumentException("url must be for a HTTP or HTTPS resource"); |
| } |
| if (token == null) { |
| throw new IllegalArgumentException("token cannot be NULL"); |
| } |
| authenticator.authenticate(url, token); |
| HttpURLConnection conn = (HttpURLConnection) url.openConnection(); |
| if (connConfigurator != null) { |
| conn = connConfigurator.configure(conn); |
| } |
| injectToken(conn, token); |
| return conn; |
| } |
| |
| /** |
| * Helper method that injects an authentication token to send with a connection. |
| * |
| * @param conn connection to inject the authentication token into. |
| * @param token authentication token to inject. |
| */ |
| public static void injectToken(HttpURLConnection conn, Token token) { |
| String t = token.token; |
| if (t != null) { |
| if (!t.startsWith("\"")) { |
| t = "\"" + t + "\""; |
| } |
| conn.addRequestProperty("Cookie", AUTH_COOKIE_EQ + t); |
| } |
| } |
| |
| /** |
| * Helper method that extracts an authentication token received from a connection. |
| * <p/> |
| * This method is used by {@link Authenticator} implementations. |
| * |
| * @param conn connection to extract the authentication token from. |
| * @param token the authentication token. |
| * |
| * @throws IOException if an IO error occurred. |
| * @throws AuthenticationException if an authentication exception occurred. |
| */ |
| public static void extractToken(HttpURLConnection conn, Token token) throws IOException, AuthenticationException { |
| if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { |
| Map<String, List<String>> headers = conn.getHeaderFields(); |
| List<String> cookies = headers.get("Set-Cookie"); |
| if (cookies != null) { |
| for (String cookie : cookies) { |
| if (cookie.startsWith(AUTH_COOKIE_EQ)) { |
| String value = cookie.substring(AUTH_COOKIE_EQ.length()); |
| int separator = value.indexOf(";"); |
| if (separator > -1) { |
| value = value.substring(0, separator); |
| } |
| if (value.length() > 0) { |
| token.set(value); |
| } |
| } |
| } |
| } |
| } else { |
| token.set(null); |
| throw new AuthenticationException("Authentication failed, status: " + conn.getResponseCode() + |
| ", message: " + conn.getResponseMessage()); |
| } |
| } |
| |
| } |