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/branches/branch-2@1418432 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
448882534b
commit
3ff00d4aaa
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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}} \]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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 + " .*"));
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue