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 0bd78f5901b..9acc4dd0675 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
@@ -13,6 +13,8 @@
*/
package org.apache.hadoop.security.authentication.server;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.security.authentication.client.AuthenticatedURL;
import org.apache.hadoop.security.authentication.client.AuthenticationException;
import org.apache.hadoop.security.authentication.util.Signer;
@@ -32,9 +34,8 @@ import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.Principal;
-import java.util.Enumeration;
-import java.util.Properties;
-import java.util.Random;
+import java.text.SimpleDateFormat;
+import java.util.*;
/**
* The {@link AuthenticationFilter} enables protecting web application resources with different (pluggable)
@@ -69,6 +70,9 @@ import java.util.Random;
* the prefix from it and it will pass them to the the authentication handler for initialization. Properties that do
* not start with the prefix will not be passed to the authentication handler initialization.
*/
+
+@InterfaceAudience.Private
+@InterfaceStability.Unstable
public class AuthenticationFilter implements Filter {
private static Logger LOG = LoggerFactory.getLogger(AuthenticationFilter.class);
@@ -331,6 +335,7 @@ public class AuthenticationFilter implements Filter {
String unauthorizedMsg = "";
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
+ boolean isHttps = "https".equals(httpRequest.getScheme());
try {
boolean newToken = false;
AuthenticationToken token;
@@ -378,8 +383,8 @@ public class AuthenticationFilter implements Filter {
};
if (newToken && !token.isExpired() && token != AuthenticationToken.ANONYMOUS) {
String signedToken = signer.sign(token.toString());
- Cookie cookie = createCookie(signedToken);
- httpResponse.addCookie(cookie);
+ createAuthCookie(httpResponse, signedToken, getCookieDomain(),
+ getCookiePath(), token.getExpires(), isHttps);
}
filterChain.doFilter(httpRequest, httpResponse);
}
@@ -392,31 +397,52 @@ public class AuthenticationFilter implements Filter {
}
if (unauthorizedResponse) {
if (!httpResponse.isCommitted()) {
- Cookie cookie = createCookie("");
- cookie.setMaxAge(0);
- httpResponse.addCookie(cookie);
- httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, unauthorizedMsg);
+ createAuthCookie(httpResponse, "", getCookieDomain(),
+ getCookiePath(), 0, isHttps);
+ httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,
+ unauthorizedMsg);
}
}
}
/**
- * Creates the Hadoop authentiation HTTP cookie.
- *
- * It sets the domain and path specified in the configuration.
+ * Creates the Hadoop authentication HTTP cookie.
*
* @param token authentication token for the cookie.
+ * @param expires UNIX timestamp that indicates the expire date of the
+ * cookie. It has no effect if its value < 0.
*
- * @return the HTTP cookie.
+ * XXX the following code duplicate some logic in Jetty / Servlet API,
+ * because of the fact that Hadoop is stuck at servlet 3.0 and jetty 6
+ * right now.
*/
- protected Cookie createCookie(String token) {
- Cookie cookie = new Cookie(AuthenticatedURL.AUTH_COOKIE, token);
- if (getCookieDomain() != null) {
- cookie.setDomain(getCookieDomain());
+ public static void createAuthCookie(HttpServletResponse resp, String token,
+ String domain, String path, long expires,
+ boolean isSecure) {
+ StringBuilder sb = new StringBuilder(AuthenticatedURL.AUTH_COOKIE).append
+ ("=").append(token);
+
+ if (path != null) {
+ sb.append("; Path=").append(path);
}
- if (getCookiePath() != null) {
- cookie.setPath(getCookiePath());
+
+ if (domain != null) {
+ sb.append("; Domain=").append(domain);
}
- return cookie;
+
+ if (expires >= 0) {
+ Date date = new Date(expires);
+ SimpleDateFormat df = new SimpleDateFormat("EEE, " +
+ "dd-MMM-yyyy HH:mm:ss zzz");
+ df.setTimeZone(TimeZone.getTimeZone("GMT"));
+ sb.append("; Expires=").append(df.format(date));
+ }
+
+ if (isSecure) {
+ sb.append("; Secure");
+ }
+
+ sb.append("; HttpOnly");
+ resp.addHeader("Set-Cookie", sb.toString());
}
}
diff --git a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestAuthenticationFilter.java b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestAuthenticationFilter.java
index 6820151210c..dcadf15f513 100644
--- a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestAuthenticationFilter.java
+++ b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestAuthenticationFilter.java
@@ -18,6 +18,7 @@ import org.apache.hadoop.security.authentication.client.AuthenticationException;
import org.apache.hadoop.security.authentication.util.Signer;
import org.junit.Assert;
import org.junit.Test;
+import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -31,9 +32,7 @@ import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
-import java.util.Arrays;
-import java.util.Properties;
-import java.util.Vector;
+import java.util.*;
public class TestAuthenticationFilter {
@@ -400,103 +399,87 @@ public class TestAuthenticationFilter {
boolean invalidToken,
boolean expired) throws Exception {
AuthenticationFilter filter = new AuthenticationFilter();
+ FilterConfig config = Mockito.mock(FilterConfig.class);
+ Mockito.when(config.getInitParameter("management.operation.return")).
+ thenReturn("true");
+ Mockito.when(config.getInitParameter("expired.token")).
+ thenReturn(Boolean.toString(expired));
+ Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE))
+ .thenReturn(DummyAuthenticationHandler.class.getName());
+ Mockito.when(config.getInitParameter(AuthenticationFilter
+ .AUTH_TOKEN_VALIDITY)).thenReturn("1000");
+ Mockito.when(config.getInitParameter(AuthenticationFilter
+ .SIGNATURE_SECRET)).thenReturn("secret");
+ Mockito.when(config.getInitParameterNames()).thenReturn(new
+ Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE,
+ AuthenticationFilter.AUTH_TOKEN_VALIDITY,
+ AuthenticationFilter.SIGNATURE_SECRET, "management.operation" +
+ ".return", "expired.token")).elements());
+
+ if (withDomainPath) {
+ Mockito.when(config.getInitParameter(AuthenticationFilter
+ .COOKIE_DOMAIN)).thenReturn(".foo.com");
+ Mockito.when(config.getInitParameter(AuthenticationFilter.COOKIE_PATH))
+ .thenReturn("/bar");
+ Mockito.when(config.getInitParameterNames()).thenReturn(new
+ Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE,
+ AuthenticationFilter.AUTH_TOKEN_VALIDITY,
+ AuthenticationFilter.SIGNATURE_SECRET,
+ AuthenticationFilter.COOKIE_DOMAIN, AuthenticationFilter
+ .COOKIE_PATH, "management.operation.return")).elements());
+ }
+
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ Mockito.when(request.getParameter("authenticated")).thenReturn("true");
+ Mockito.when(request.getRequestURL()).thenReturn(new StringBuffer
+ ("http://foo:8080/bar"));
+ Mockito.when(request.getQueryString()).thenReturn("authenticated=true");
+
+ if (invalidToken) {
+ Mockito.when(request.getCookies()).thenReturn(new Cookie[]{new Cookie
+ (AuthenticatedURL.AUTH_COOKIE, "foo")});
+ }
+
+ HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+ FilterChain chain = Mockito.mock(FilterChain.class);
+
+ final HashMap cookieMap = new HashMap();
+ Mockito.doAnswer(new Answer