HADOOP-9054. Add AuthenticationHandler that uses Kerberos but allows for an alternate form of authentication for browsers. (rkanter via tucu)

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1418429 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Alejandro Abdelnur 2012-12-07 18:46:41 +00:00
parent ad619d34d0
commit 0b11245d34
7 changed files with 361 additions and 15 deletions

View File

@ -110,6 +110,7 @@
<exclude>**/${test.exclude}.java</exclude>
<exclude>${test.exclude.pattern}</exclude>
<exclude>**/TestKerberosAuth*.java</exclude>
<exclude>**/TestAltKerberosAuth*.java</exclude>
<exclude>**/Test*$*.java</exclude>
</excludes>
</configuration>

View File

@ -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.
* <p/>
*/
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'.
* <p/>
*
* @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).
* <p/>
*
* @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.
* <p/>
* 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;
}

View File

@ -176,6 +176,73 @@ Configuration
...
</web-app>
+---+
** AltKerberos Configuration
<<IMPORTANT>>: 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.
<<Example>>:
+---+
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee">
...
<filter>
<filter-name>kerberosFilter</filter-name>
<filter-class>org.apache.hadoop.security.auth.server.AuthenticationFilter</filter-class>
<init-param>
<param-name>type</param-name>
<param-value>org.my.subclass.of.AltKerberosAuthenticationHandler</param-value>
</init-param>
<init-param>
<param-name>alt-kerberos.non-browser.user-agents</param-name>
<param-value>java,curl,wget,perl</param-value>
</init-param>
<init-param>
<param-name>token.validity</param-name>
<param-value>30</param-value>
</init-param>
<init-param>
<param-name>cookie.domain</param-name>
<param-value>.foo.com</param-value>
</init-param>
<init-param>
<param-name>cookie.path</param-name>
<param-value>/</param-value>
</init-param>
<init-param>
<param-name>kerberos.principal</param-name>
<param-value>HTTP/localhost@LOCALHOST</param-value>
</init-param>
<init-param>
<param-name>kerberos.keytab</param-name>
<param-value>/tmp/auth.keytab</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>kerberosFilter</filter-name>
<url-pattern>/kerberos/*</url-pattern>
</filter-mapping>
...
</web-app>
+---+
\[ {{{./index.html}Go Back}} \]

View File

@ -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

View File

@ -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();
}
}

View File

@ -28,23 +28,37 @@ import org.ietf.jgss.Oid;
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 class TestKerberosAuthenticationHandler extends TestCase {
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 class TestKerberosAuthenticationHandler extends TestCase {
}
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 class TestKerberosAuthenticationHandler extends TestCase {
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 + " .*"));

View File

@ -312,6 +312,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