diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationFilter.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationFilter.java index 0f86623adae..bf44f48ca2a 100644 --- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationFilter.java +++ b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationFilter.java @@ -160,6 +160,12 @@ public class AuthenticationFilter implements Filter { */ public static final String COOKIE_PATH = "cookie.path"; + /** + * Constant for the configuration property + * that indicates the persistence of the HTTP cookie. + */ + public static final String COOKIE_PERSISTENT = "cookie.persistent"; + /** * Constant for the configuration property that indicates the name of the * SignerSecretProvider class to use. @@ -187,6 +193,7 @@ public class AuthenticationFilter implements Filter { private long validity; private String cookieDomain; private String cookiePath; + private boolean isCookiePersistent; private boolean isInitializedByTomcat; /** @@ -228,6 +235,9 @@ public class AuthenticationFilter implements Filter { cookieDomain = config.getProperty(COOKIE_DOMAIN, null); cookiePath = config.getProperty(COOKIE_PATH, null); + isCookiePersistent = Boolean.parseBoolean( + config.getProperty(COOKIE_PERSISTENT, "false")); + } protected void initializeAuthHandler(String authHandlerClassName, FilterConfig filterConfig) @@ -371,6 +381,15 @@ public class AuthenticationFilter implements Filter { return cookiePath; } + /** + * Returns the cookie persistence to use for the HTTP cookie. + * + * @return the cookie persistence to use for the HTTP cookie. + */ + protected boolean isCookiePersistent() { + return isCookiePersistent; + } + /** * Destroys the filter. *

@@ -549,7 +568,8 @@ public class AuthenticationFilter implements Filter { if (newToken && !token.isExpired() && token != AuthenticationToken.ANONYMOUS) { String signedToken = signer.sign(token.toString()); createAuthCookie(httpResponse, signedToken, getCookieDomain(), - getCookiePath(), token.getExpires(), isHttps); + getCookiePath(), token.getExpires(), + isCookiePersistent(), isHttps); } doFilter(filterChain, httpRequest, httpResponse); } @@ -569,7 +589,7 @@ public class AuthenticationFilter implements Filter { if (unauthorizedResponse) { if (!httpResponse.isCommitted()) { createAuthCookie(httpResponse, "", getCookieDomain(), - getCookiePath(), 0, isHttps); + getCookiePath(), 0, isCookiePersistent(), isHttps); // If response code is 401. Then WWW-Authenticate Header should be // present.. reset to 403 if not found.. if ((errCode == HttpServletResponse.SC_UNAUTHORIZED) @@ -614,6 +634,7 @@ public class AuthenticationFilter implements Filter { * @param isSecure is the cookie secure? * @param token the token. * @param expires the cookie expiration time. + * @param isCookiePersistent whether the cookie is persistent or not. * * XXX the following code duplicate some logic in Jetty / Servlet API, * because of the fact that Hadoop is stuck at servlet 2.5 and jetty 6 @@ -621,6 +642,7 @@ public class AuthenticationFilter implements Filter { */ public static void createAuthCookie(HttpServletResponse resp, String token, String domain, String path, long expires, + boolean isCookiePersistent, boolean isSecure) { StringBuilder sb = new StringBuilder(AuthenticatedURL.AUTH_COOKIE) .append("="); @@ -636,7 +658,7 @@ public class AuthenticationFilter implements Filter { sb.append("; Domain=").append(domain); } - if (expires >= 0) { + if (expires >= 0 && isCookiePersistent) { Date date = new Date(expires); SimpleDateFormat df = new SimpleDateFormat("EEE, " + "dd-MMM-yyyy HH:mm:ss zzz"); diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestAuthenticationSessionCookie.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestAuthenticationSessionCookie.java new file mode 100644 index 00000000000..e435034cc60 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestAuthenticationSessionCookie.java @@ -0,0 +1,187 @@ +/** + * 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.http; + +import org.junit.Assert; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FileUtil; +import org.apache.hadoop.net.NetUtils; +import org.apache.hadoop.security.authentication.server.AuthenticationFilter; +import org.apache.hadoop.security.ssl.KeyStoreTestUtil; +import org.junit.After; +import org.junit.Test; +import org.mortbay.log.Log; + +import javax.servlet.*; +import javax.servlet.http.HttpServletResponse; + +import java.io.File; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.net.HttpCookie; +import java.util.List; + +public class TestAuthenticationSessionCookie { + private static final String BASEDIR = System.getProperty("test.build.dir", + "target/test-dir") + "/" + TestHttpCookieFlag.class.getSimpleName(); + private static boolean isCookiePersistent; + private static final long TOKEN_VALIDITY_SEC = 1000; + private static long expires; + private static String keystoresDir; + private static String sslConfDir; + private static HttpServer2 server; + + public static class DummyAuthenticationFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + isCookiePersistent = false; + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, + ServletException { + HttpServletResponse resp = (HttpServletResponse) response; + AuthenticationFilter.createAuthCookie(resp, "token", null, null, expires, + isCookiePersistent, true); + chain.doFilter(request, resp); + } + + @Override + public void destroy() { + } + } + + public static class DummyFilterInitializer extends FilterInitializer { + @Override + public void initFilter(FilterContainer container, Configuration conf) { + container.addFilter("DummyAuth", DummyAuthenticationFilter.class + .getName(), null); + } + } + + public static class Dummy2AuthenticationFilter + extends DummyAuthenticationFilter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + isCookiePersistent = true; + expires = System.currentTimeMillis() + TOKEN_VALIDITY_SEC; + } + + @Override + public void destroy() { + } + } + + public static class Dummy2FilterInitializer extends FilterInitializer { + @Override + public void initFilter(FilterContainer container, Configuration conf) { + container.addFilter("Dummy2Auth", Dummy2AuthenticationFilter.class + .getName(), null); + } + } + + public void startServer(boolean isTestSessionCookie) throws Exception { + Configuration conf = new Configuration(); + if (isTestSessionCookie) { + conf.set(HttpServer2.FILTER_INITIALIZER_PROPERTY, + DummyFilterInitializer.class.getName()); + } else { + conf.set(HttpServer2.FILTER_INITIALIZER_PROPERTY, + Dummy2FilterInitializer.class.getName()); + } + + File base = new File(BASEDIR); + FileUtil.fullyDelete(base); + base.mkdirs(); + keystoresDir = new File(BASEDIR).getAbsolutePath(); + sslConfDir = KeyStoreTestUtil.getClasspathDir(TestSSLHttpServer.class); + + KeyStoreTestUtil.setupSSLConfig(keystoresDir, sslConfDir, conf, false); + Configuration sslConf = new Configuration(false); + sslConf.addResource("ssl-server.xml"); + sslConf.addResource("ssl-client.xml"); + + + server = new HttpServer2.Builder() + .setName("test") + .addEndpoint(new URI("http://localhost")) + .addEndpoint(new URI("https://localhost")) + .setConf(conf) + .keyPassword(sslConf.get("ssl.server.keystore.keypassword")) + .keyStore(sslConf.get("ssl.server.keystore.location"), + sslConf.get("ssl.server.keystore.password"), + sslConf.get("ssl.server.keystore.type", "jks")) + .trustStore(sslConf.get("ssl.server.truststore.location"), + sslConf.get("ssl.server.truststore.password"), + sslConf.get("ssl.server.truststore.type", "jks")).build(); + server.addServlet("echo", "/echo", TestHttpServer.EchoServlet.class); + server.start(); + } + + @Test + public void testSessionCookie() throws IOException { + try { + startServer(true); + } catch (Exception e) { + // Auto-generated catch block + e.printStackTrace(); + } + + URL base = new URL("http://" + NetUtils.getHostPortString(server + .getConnectorAddress(0))); + HttpURLConnection conn = (HttpURLConnection) new URL(base, + "/echo").openConnection(); + + String header = conn.getHeaderField("Set-Cookie"); + List cookies = HttpCookie.parse(header); + Assert.assertTrue(!cookies.isEmpty()); + Log.info(header); + Assert.assertFalse(header.contains("; Expires=")); + Assert.assertTrue("token".equals(cookies.get(0).getValue())); + } + + @Test + public void testPersistentCookie() throws IOException { + try { + startServer(false); + } catch (Exception e) { + // Auto-generated catch block + e.printStackTrace(); + } + + URL base = new URL("http://" + NetUtils.getHostPortString(server + .getConnectorAddress(0))); + HttpURLConnection conn = (HttpURLConnection) new URL(base, + "/echo").openConnection(); + + String header = conn.getHeaderField("Set-Cookie"); + List cookies = HttpCookie.parse(header); + Assert.assertTrue(!cookies.isEmpty()); + Log.info(header); + Assert.assertTrue(header.contains("; Expires=")); + Assert.assertTrue("token".equals(cookies.get(0).getValue())); + } + + @After + public void cleanup() throws Exception { + server.stop(); + FileUtil.fullyDelete(new File(BASEDIR)); + KeyStoreTestUtil.cleanupSSLConfig(keystoresDir, sslConfDir); + } +} diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpCookieFlag.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpCookieFlag.java index 75a94804824..5c5ed482111 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpCookieFlag.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpCookieFlag.java @@ -60,7 +60,7 @@ public class TestHttpCookieFlag { HttpServletResponse resp = (HttpServletResponse) response; boolean isHttps = "https".equals(request.getScheme()); AuthenticationFilter.createAuthCookie(resp, "token", null, null, -1, - isHttps); + true, isHttps); chain.doFilter(request, resp); }