diff --git a/hadoop-common-project/hadoop-auth/pom.xml b/hadoop-common-project/hadoop-auth/pom.xml
index d7e1a016f72..0f9a6d1ddab 100644
--- a/hadoop-common-project/hadoop-auth/pom.xml
+++ b/hadoop-common-project/hadoop-auth/pom.xml
@@ -110,6 +110,7 @@
**/${test.exclude}.java
${test.exclude.pattern}
**/TestKerberosAuth*.java
+ **/TestAltKerberosAuth*.java
**/Test*$*.java
diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AltKerberosAuthenticationHandler.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AltKerberosAuthenticationHandler.java
new file mode 100644
index 00000000000..e786e37df8e
--- /dev/null
+++ b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AltKerberosAuthenticationHandler.java
@@ -0,0 +1,150 @@
+/**
+ * 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 java.io.IOException;
+import java.util.Properties;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.hadoop.security.authentication.client.AuthenticationException;
+
+ /**
+ * The {@link AltKerberosAuthenticationHandler} behaves exactly the same way as
+ * the {@link KerberosAuthenticationHandler}, except that it allows for an
+ * alternative form of authentication for browsers while still using Kerberos
+ * for Java access. This is an abstract class that should be subclassed
+ * to allow a developer to implement their own custom authentication for browser
+ * access. The alternateAuthenticate method will be called whenever a request
+ * comes from a browser.
+ *
+ */
+public abstract class AltKerberosAuthenticationHandler
+ extends KerberosAuthenticationHandler {
+
+ /**
+ * Constant that identifies the authentication mechanism.
+ */
+ public static final String TYPE = "alt-kerberos";
+
+ /**
+ * Constant for the configuration property that indicates which user agents
+ * are not considered browsers (comma separated)
+ */
+ public static final String NON_BROWSER_USER_AGENTS =
+ TYPE + ".non-browser.user-agents";
+ private static final String NON_BROWSER_USER_AGENTS_DEFAULT =
+ "java,curl,wget,perl";
+
+ private String[] nonBrowserUserAgents;
+
+ /**
+ * Returns the authentication type of the authentication handler,
+ * 'alt-kerberos'.
+ *
+ *
+ * @return the authentication type of the authentication handler,
+ * 'alt-kerberos'.
+ */
+ @Override
+ public String getType() {
+ return TYPE;
+ }
+
+ @Override
+ public void init(Properties config) throws ServletException {
+ super.init(config);
+
+ nonBrowserUserAgents = config.getProperty(
+ NON_BROWSER_USER_AGENTS, NON_BROWSER_USER_AGENTS_DEFAULT)
+ .split("\\W*,\\W*");
+ for (int i = 0; i < nonBrowserUserAgents.length; i++) {
+ nonBrowserUserAgents[i] = nonBrowserUserAgents[i].toLowerCase();
+ }
+ }
+
+ /**
+ * It enforces the the Kerberos SPNEGO authentication sequence returning an
+ * {@link AuthenticationToken} only after the Kerberos SPNEGO sequence has
+ * completed successfully (in the case of Java access) and only after the
+ * custom authentication implemented by the subclass in alternateAuthenticate
+ * has completed successfully (in the case of browser access).
+ *
+ *
+ * @param request the HTTP client request.
+ * @param response the HTTP client response.
+ *
+ * @return an authentication token if the request is authorized or null
+ *
+ * @throws IOException thrown if an IO error occurred
+ * @throws AuthenticationException thrown if an authentication error occurred
+ */
+ @Override
+ public AuthenticationToken authenticate(HttpServletRequest request,
+ HttpServletResponse response)
+ throws IOException, AuthenticationException {
+ AuthenticationToken token;
+ if (isBrowser(request.getHeader("User-Agent"))) {
+ token = alternateAuthenticate(request, response);
+ }
+ else {
+ token = super.authenticate(request, response);
+ }
+ return token;
+ }
+
+ /**
+ * This method parses the User-Agent String and returns whether or not it
+ * refers to a browser. If its not a browser, then Kerberos authentication
+ * will be used; if it is a browser, alternateAuthenticate from the subclass
+ * will be used.
+ *
+ * A User-Agent String is considered to be a browser if it does not contain
+ * any of the values from alt-kerberos.non-browser.user-agents; the default
+ * behavior is to consider everything a browser unless it contains one of:
+ * "java", "curl", "wget", or "perl". Subclasses can optionally override
+ * this method to use different behavior.
+ *
+ * @param userAgent The User-Agent String, or null if there isn't one
+ * @return true if the User-Agent String refers to a browser, false if not
+ */
+ protected boolean isBrowser(String userAgent) {
+ if (userAgent == null) {
+ return false;
+ }
+ userAgent = userAgent.toLowerCase();
+ boolean isBrowser = true;
+ for (String nonBrowserUserAgent : nonBrowserUserAgents) {
+ if (userAgent.contains(nonBrowserUserAgent)) {
+ isBrowser = false;
+ break;
+ }
+ }
+ return isBrowser;
+ }
+
+ /**
+ * Subclasses should implement this method to provide the custom
+ * authentication to be used for browsers.
+ *
+ * @param request the HTTP client request.
+ * @param response the HTTP client response.
+ * @return an authentication token if the request is authorized, or null
+ * @throws IOException thrown if an IO error occurs
+ * @throws AuthenticationException thrown if an authentication error occurs
+ */
+ public abstract AuthenticationToken alternateAuthenticate(
+ HttpServletRequest request, HttpServletResponse response)
+ throws IOException, AuthenticationException;
+}
diff --git a/hadoop-common-project/hadoop-auth/src/site/apt/Configuration.apt.vm b/hadoop-common-project/hadoop-auth/src/site/apt/Configuration.apt.vm
index e42ee8b4c30..f2fe11d8f6d 100644
--- a/hadoop-common-project/hadoop-auth/src/site/apt/Configuration.apt.vm
+++ b/hadoop-common-project/hadoop-auth/src/site/apt/Configuration.apt.vm
@@ -176,6 +176,73 @@ Configuration
...
++---+
+
+** AltKerberos Configuration
+
+ <>: A KDC must be configured and running.
+
+ The AltKerberos authentication mechanism is a partially implemented derivative
+ of the Kerberos SPNEGO authentication mechanism which allows a "mixed" form of
+ authentication where Kerberos SPNEGO is used by non-browsers while an
+ alternate form of authentication (to be implemented by the user) is used for
+ browsers. To use AltKerberos as the authentication mechanism (besides
+ providing an implementation), the authentication filter must be configured
+ with the following init parameters, in addition to the previously mentioned
+ Kerberos SPNEGO ones:
+
+ * <<<[PREFIX.]type>>>: the full class name of the implementation of
+ AltKerberosAuthenticationHandler to use.
+
+ * <<<[PREFIX.]alt-kerberos.non-browser.user-agents>>>: a comma-separated
+ list of which user-agents should be considered non-browsers.
+
+ <>:
+
++---+
+
+ ...
+
+
+ kerberosFilter
+ org.apache.hadoop.security.auth.server.AuthenticationFilter
+
+ type
+ org.my.subclass.of.AltKerberosAuthenticationHandler
+
+
+ alt-kerberos.non-browser.user-agents
+ java,curl,wget,perl
+
+
+ token.validity
+ 30
+
+
+ cookie.domain
+ .foo.com
+
+
+ cookie.path
+ /
+
+
+ kerberos.principal
+ HTTP/localhost@LOCALHOST
+
+
+ kerberos.keytab
+ /tmp/auth.keytab
+
+
+
+
+ kerberosFilter
+ /kerberos/*
+
+
+ ...
+
+---+
\[ {{{./index.html}Go Back}} \]
diff --git a/hadoop-common-project/hadoop-auth/src/site/apt/index.apt.vm b/hadoop-common-project/hadoop-auth/src/site/apt/index.apt.vm
index a2e7b5e915a..26fc2492ca0 100644
--- a/hadoop-common-project/hadoop-auth/src/site/apt/index.apt.vm
+++ b/hadoop-common-project/hadoop-auth/src/site/apt/index.apt.vm
@@ -24,6 +24,11 @@ Hadoop Auth, Java HTTP SPNEGO ${project.version}
Hadoop Auth also supports additional authentication mechanisms on the client
and the server side via 2 simple interfaces.
+ Additionally, it provides a partially implemented derivative of the Kerberos
+ SPNEGO authentication to allow a "mixed" form of authentication where Kerberos
+ SPNEGO is used by non-browsers while an alternate form of authentication
+ (to be implemented by the user) is used for browsers.
+
* License
Hadoop Auth is distributed under {{{http://www.apache.org/licenses/}Apache
diff --git a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestAltKerberosAuthenticationHandler.java b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestAltKerberosAuthenticationHandler.java
new file mode 100644
index 00000000000..c2d43ebb3ca
--- /dev/null
+++ b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestAltKerberosAuthenticationHandler.java
@@ -0,0 +1,110 @@
+/**
+ * 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 java.io.IOException;
+import java.util.Properties;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.hadoop.security.authentication.client.AuthenticationException;
+import org.mockito.Mockito;
+
+public class TestAltKerberosAuthenticationHandler
+ extends TestKerberosAuthenticationHandler {
+
+ @Override
+ protected KerberosAuthenticationHandler getNewAuthenticationHandler() {
+ // AltKerberosAuthenticationHandler is abstract; a subclass would normally
+ // perform some other authentication when alternateAuthenticate() is called.
+ // For the test, we'll just return an AuthenticationToken as the other
+ // authentication is left up to the developer of the subclass
+ return new AltKerberosAuthenticationHandler() {
+ @Override
+ public AuthenticationToken alternateAuthenticate(
+ HttpServletRequest request,
+ HttpServletResponse response)
+ throws IOException, AuthenticationException {
+ return new AuthenticationToken("A", "B", getType());
+ }
+ };
+ }
+
+ @Override
+ protected String getExpectedType() {
+ return AltKerberosAuthenticationHandler.TYPE;
+ }
+
+ public void testAlternateAuthenticationAsBrowser() throws Exception {
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+
+ // By default, a User-Agent without "java", "curl", "wget", or "perl" in it
+ // is considered a browser
+ Mockito.when(request.getHeader("User-Agent")).thenReturn("Some Browser");
+
+ AuthenticationToken token = handler.authenticate(request, response);
+ assertEquals("A", token.getUserName());
+ assertEquals("B", token.getName());
+ assertEquals(getExpectedType(), token.getType());
+ }
+
+ public void testNonDefaultNonBrowserUserAgentAsBrowser() throws Exception {
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+
+ if (handler != null) {
+ handler.destroy();
+ handler = null;
+ }
+ handler = getNewAuthenticationHandler();
+ Properties props = getDefaultProperties();
+ props.setProperty("alt-kerberos.non-browser.user-agents", "foo, bar");
+ try {
+ handler.init(props);
+ } catch (Exception ex) {
+ handler = null;
+ throw ex;
+ }
+
+ // Pretend we're something that will not match with "foo" (or "bar")
+ Mockito.when(request.getHeader("User-Agent")).thenReturn("blah");
+ // Should use alt authentication
+ AuthenticationToken token = handler.authenticate(request, response);
+ assertEquals("A", token.getUserName());
+ assertEquals("B", token.getName());
+ assertEquals(getExpectedType(), token.getType());
+ }
+
+ public void testNonDefaultNonBrowserUserAgentAsNonBrowser() throws Exception {
+ if (handler != null) {
+ handler.destroy();
+ handler = null;
+ }
+ handler = getNewAuthenticationHandler();
+ Properties props = getDefaultProperties();
+ props.setProperty("alt-kerberos.non-browser.user-agents", "foo, bar");
+ try {
+ handler.init(props);
+ } catch (Exception ex) {
+ handler = null;
+ throw ex;
+ }
+
+ // Run the kerberos tests again
+ testRequestWithoutAuthorization();
+ testRequestWithInvalidAuthorization();
+ testRequestWithAuthorization();
+ testRequestWithInvalidKerberosAuthorization();
+ }
+}
diff --git a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestKerberosAuthenticationHandler.java b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestKerberosAuthenticationHandler.java
index 692ceab92da..d198e58431d 100644
--- a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestKerberosAuthenticationHandler.java
+++ b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestKerberosAuthenticationHandler.java
@@ -28,23 +28,37 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import java.lang.reflect.Field;
import java.util.Properties;
import java.util.concurrent.Callable;
public class TestKerberosAuthenticationHandler extends TestCase {
- private KerberosAuthenticationHandler handler;
+ protected KerberosAuthenticationHandler handler;
+
+ protected KerberosAuthenticationHandler getNewAuthenticationHandler() {
+ return new KerberosAuthenticationHandler();
+ }
+
+ protected String getExpectedType() {
+ return KerberosAuthenticationHandler.TYPE;
+ }
+
+ protected Properties getDefaultProperties() {
+ Properties props = new Properties();
+ props.setProperty(KerberosAuthenticationHandler.PRINCIPAL,
+ KerberosTestUtils.getServerPrincipal());
+ props.setProperty(KerberosAuthenticationHandler.KEYTAB,
+ KerberosTestUtils.getKeytabFile());
+ props.setProperty(KerberosAuthenticationHandler.NAME_RULES,
+ "RULE:[1:$1@$0](.*@" + KerberosTestUtils.getRealm()+")s/@.*//\n");
+ return props;
+ }
@Override
protected void setUp() throws Exception {
super.setUp();
- handler = new KerberosAuthenticationHandler();
- Properties props = new Properties();
- props.setProperty(KerberosAuthenticationHandler.PRINCIPAL, KerberosTestUtils.getServerPrincipal());
- props.setProperty(KerberosAuthenticationHandler.KEYTAB, KerberosTestUtils.getKeytabFile());
- props.setProperty(KerberosAuthenticationHandler.NAME_RULES,
- "RULE:[1:$1@$0](.*@" + KerberosTestUtils.getRealm()+")s/@.*//\n");
+ handler = getNewAuthenticationHandler();
+ Properties props = getDefaultProperties();
try {
handler.init(props);
} catch (Exception ex) {
@@ -71,10 +85,8 @@ public void testNameRules() throws Exception {
KerberosName.setRules("RULE:[1:$1@$0](.*@FOO)s/@.*//\nDEFAULT");
- handler = new KerberosAuthenticationHandler();
- Properties props = new Properties();
- props.setProperty(KerberosAuthenticationHandler.PRINCIPAL, KerberosTestUtils.getServerPrincipal());
- props.setProperty(KerberosAuthenticationHandler.KEYTAB, KerberosTestUtils.getKeytabFile());
+ handler = getNewAuthenticationHandler();
+ Properties props = getDefaultProperties();
props.setProperty(KerberosAuthenticationHandler.NAME_RULES, "RULE:[1:$1@$0](.*@BAR)s/@.*//\nDEFAULT");
try {
handler.init(props);
@@ -97,8 +109,7 @@ public void testInit() throws Exception {
}
public void testType() throws Exception {
- KerberosAuthenticationHandler handler = new KerberosAuthenticationHandler();
- assertEquals(KerberosAuthenticationHandler.TYPE, handler.getType());
+ assertEquals(getExpectedType(), handler.getType());
}
public void testRequestWithoutAuthorization() throws Exception {
@@ -182,7 +193,7 @@ public String call() throws Exception {
assertEquals(KerberosTestUtils.getClientPrincipal(), authToken.getName());
assertTrue(KerberosTestUtils.getClientPrincipal().startsWith(authToken.getUserName()));
- assertEquals(KerberosAuthenticationHandler.TYPE, authToken.getType());
+ assertEquals(getExpectedType(), authToken.getType());
} else {
Mockito.verify(response).setHeader(Mockito.eq(KerberosAuthenticator.WWW_AUTHENTICATE),
Mockito.matches(KerberosAuthenticator.NEGOTIATE + " .*"));
diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt
index c72a153e2ac..8cdf3264061 100644
--- a/hadoop-common-project/hadoop-common/CHANGES.txt
+++ b/hadoop-common-project/hadoop-common/CHANGES.txt
@@ -14,6 +14,8 @@ Release 2.0.3-alpha - Unreleased
HADOOP-9090. Support on-demand publish of metrics. (Mostafa Elhemali via
suresh)
+ HADOOP-9054. Add AuthenticationHandler that uses Kerberos but allows for
+ an alternate form of authentication for browsers. (rkanter via tucu)
IMPROVEMENTS