Browse Source

SEC-3108: DigestAuthenticationFilter should use SecurityContextHolder.createEmptyContext()

Rob Winch 9 years ago
parent
commit
c64b80564e

+ 137 - 88
web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java

@@ -38,6 +38,7 @@ import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.SpringSecurityMessageSource;
 import org.springframework.security.crypto.codec.Base64;
+import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.userdetails.UserCache;
 import org.springframework.security.core.userdetails.UserDetails;
@@ -49,46 +50,52 @@ import org.springframework.util.Assert;
 import org.springframework.util.StringUtils;
 import org.springframework.web.filter.GenericFilterBean;
 
-
 /**
  * Processes a HTTP request's Digest authorization headers, putting the result into the
  * <code>SecurityContextHolder</code>.
  * <p>
- * For a detailed background on what this filter is designed to process, refer to
- * <a href="http://www.ietf.org/rfc/rfc2617.txt">RFC 2617</a> (which superseded RFC 2069, although this
- * filter support clients that implement either RFC 2617 or RFC 2069).
+ * For a detailed background on what this filter is designed to process, refer to <a
+ * href="http://www.ietf.org/rfc/rfc2617.txt">RFC 2617</a> (which superseded RFC 2069,
+ * although this filter support clients that implement either RFC 2617 or RFC 2069).
  * <p>
- * This filter can be used to provide Digest authentication services to both remoting protocol clients (such as
- * Hessian and SOAP) as well as standard user agents (such as Internet Explorer and FireFox).
+ * This filter can be used to provide Digest authentication services to both remoting
+ * protocol clients (such as Hessian and SOAP) as well as standard user agents (such as
+ * Internet Explorer and FireFox).
  * <p>
- * This Digest implementation has been designed to avoid needing to store session state between invocations.
- * All session management information is stored in the "nonce" that is sent to the client by the {@link
- * DigestAuthenticationEntryPoint}.
+ * This Digest implementation has been designed to avoid needing to store session state
+ * between invocations. All session management information is stored in the "nonce" that
+ * is sent to the client by the {@link DigestAuthenticationEntryPoint}.
  * <p>
- * If authentication is successful, the resulting {@link org.springframework.security.core.Authentication Authentication}
- * object will be placed into the <code>SecurityContextHolder</code>.
+ * If authentication is successful, the resulting
+ * {@link org.springframework.security.core.Authentication Authentication} object will be
+ * placed into the <code>SecurityContextHolder</code>.
  * <p>
- * If authentication fails, an {@link org.springframework.security.web.AuthenticationEntryPoint AuthenticationEntryPoint}
- * implementation is called. This must always be {@link DigestAuthenticationEntryPoint}, which will prompt the user
- * to authenticate again via Digest authentication.
+ * If authentication fails, an
+ * {@link org.springframework.security.web.AuthenticationEntryPoint
+ * AuthenticationEntryPoint} implementation is called. This must always be
+ * {@link DigestAuthenticationEntryPoint}, which will prompt the user to authenticate
+ * again via Digest authentication.
  * <p>
- * Note there are limitations to Digest authentication, although it is a more comprehensive and secure solution
- * than Basic authentication. Please see RFC 2617 section 4 for a full discussion on the advantages of Digest
- * authentication over Basic authentication, including commentary on the limitations that it still imposes.
+ * Note there are limitations to Digest authentication, although it is a more
+ * comprehensive and secure solution than Basic authentication. Please see RFC 2617
+ * section 4 for a full discussion on the advantages of Digest authentication over Basic
+ * authentication, including commentary on the limitations that it still imposes.
  *
  * @author Ben Alex
  * @author Luke Taylor
  * @since 1.0.0
  */
-public class DigestAuthenticationFilter extends GenericFilterBean implements MessageSourceAware {
-    //~ Static fields/initializers =====================================================================================
-
+public class DigestAuthenticationFilter extends GenericFilterBean implements
+        MessageSourceAware {
+    // ~ Static fields/initializers
+    // =====================================================================================
 
     private static final Log logger = LogFactory.getLog(DigestAuthenticationFilter.class);
 
-    //~ Instance fields ================================================================================================
+    // ~ Instance fields
+    // ================================================================================================
 
-    private AuthenticationDetailsSource<HttpServletRequest,?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
+    private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
     private DigestAuthenticationEntryPoint authenticationEntryPoint;
     protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
     private UserCache userCache = new NullUserCache();
@@ -96,12 +103,14 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
     private boolean passwordAlreadyEncoded = false;
     private boolean createAuthenticatedToken = false;
 
-    //~ Methods ========================================================================================================
+    // ~ Methods
+    // ========================================================================================================
 
     @Override
     public void afterPropertiesSet() {
         Assert.notNull(userDetailsService, "A UserDetailsService is required");
-        Assert.notNull(authenticationEntryPoint, "A DigestAuthenticationEntryPoint is required");
+        Assert.notNull(authenticationEntryPoint,
+                "A DigestAuthenticationEntryPoint is required");
     }
 
     public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
@@ -118,14 +127,17 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
         }
 
         if (logger.isDebugEnabled()) {
-            logger.debug("Digest Authorization header received from user agent: " + header);
+            logger.debug("Digest Authorization header received from user agent: "
+                    + header);
         }
 
         DigestData digestAuth = new DigestData(header);
 
         try {
-            digestAuth.validateAndDecode(authenticationEntryPoint.getKey(), authenticationEntryPoint.getRealmName());
-        } catch (BadCredentialsException e) {
+            digestAuth.validateAndDecode(authenticationEntryPoint.getKey(),
+                    authenticationEntryPoint.getRealmName());
+        }
+        catch (BadCredentialsException e) {
             fail(request, response, e);
 
             return;
@@ -151,38 +163,45 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
                 userCache.putUserInCache(user);
             }
 
-            serverDigestMd5 = digestAuth.calculateServerDigest(user.getPassword(), request.getMethod());
+            serverDigestMd5 = digestAuth.calculateServerDigest(user.getPassword(),
+                    request.getMethod());
 
             // If digest is incorrect, try refreshing from backend and recomputing
             if (!serverDigestMd5.equals(digestAuth.getResponse()) && cacheWasUsed) {
                 if (logger.isDebugEnabled()) {
-                    logger.debug(
-                            "Digest comparison failure; trying to refresh user from DAO in case password had changed");
+                    logger.debug("Digest comparison failure; trying to refresh user from DAO in case password had changed");
                 }
 
                 user = userDetailsService.loadUserByUsername(digestAuth.getUsername());
                 userCache.putUserInCache(user);
-                serverDigestMd5 = digestAuth.calculateServerDigest(user.getPassword(), request.getMethod());
+                serverDigestMd5 = digestAuth.calculateServerDigest(user.getPassword(),
+                        request.getMethod());
             }
 
-        } catch (UsernameNotFoundException notFound) {
-            fail(request, response,
-                    new BadCredentialsException(messages.getMessage("DigestAuthenticationFilter.usernameNotFound",
-                            new Object[]{digestAuth.getUsername()}, "Username {0} not found")));
+        }
+        catch (UsernameNotFoundException notFound) {
+            fail(request,
+                    response,
+                    new BadCredentialsException(messages.getMessage(
+                            "DigestAuthenticationFilter.usernameNotFound",
+                            new Object[] { digestAuth.getUsername() },
+                            "Username {0} not found")));
 
             return;
         }
 
-
         // If digest is still incorrect, definitely reject authentication attempt
         if (!serverDigestMd5.equals(digestAuth.getResponse())) {
             if (logger.isDebugEnabled()) {
-                logger.debug("Expected response: '" + serverDigestMd5 + "' but received: '" + digestAuth.getResponse()
+                logger.debug("Expected response: '" + serverDigestMd5
+                        + "' but received: '" + digestAuth.getResponse()
                         + "'; is AuthenticationDao returning clear text passwords?");
             }
 
-            fail(request, response,
-                    new BadCredentialsException(messages.getMessage("DigestAuthenticationFilter.incorrectResponse",
+            fail(request,
+                    response,
+                    new BadCredentialsException(messages.getMessage(
+                            "DigestAuthenticationFilter.incorrectResponse",
                             "Incorrect response")));
             return;
         }
@@ -192,8 +211,10 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
         // We do this last so we can direct the user agent its nonce is stale
         // but the request was otherwise appearing to be valid
         if (digestAuth.isNonceExpired()) {
-            fail(request, response,
-                    new NonceExpiredException(messages.getMessage("DigestAuthenticationFilter.nonceExpired",
+            fail(request,
+                    response,
+                    new NonceExpiredException(messages.getMessage(
+                            "DigestAuthenticationFilter.nonceExpired",
                             "Nonce has expired/timed out")));
 
             return;
@@ -204,27 +225,34 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
                     + "' with response: '" + digestAuth.getResponse() + "'");
         }
 
-        SecurityContextHolder.getContext().setAuthentication(createSuccessfulAuthentication(request, user));
+        Authentication authentication = createSuccessfulAuthentication(request, user);
+        SecurityContext context = SecurityContextHolder.createEmptyContext();
+        context.setAuthentication(authentication);
+        SecurityContextHolder.setContext(context);
 
         chain.doFilter(request, response);
     }
 
-    private Authentication createSuccessfulAuthentication(HttpServletRequest request, UserDetails user) {
+    private Authentication createSuccessfulAuthentication(HttpServletRequest request,
+            UserDetails user) {
         UsernamePasswordAuthenticationToken authRequest;
         if (createAuthenticatedToken) {
-            authRequest = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
+            authRequest = new UsernamePasswordAuthenticationToken(user,
+                    user.getPassword(), user.getAuthorities());
         }
         else {
-            authRequest = new UsernamePasswordAuthenticationToken(user, user.getPassword());
+            authRequest = new UsernamePasswordAuthenticationToken(user,
+                    user.getPassword());
         }
 
-        authRequest.setDetails(authenticationDetailsSource.buildDetails((HttpServletRequest) request));
+        authRequest.setDetails(authenticationDetailsSource
+                .buildDetails((HttpServletRequest) request));
 
         return authRequest;
     }
 
-    private void fail(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)
-            throws IOException, ServletException {
+    private void fail(HttpServletRequest request, HttpServletResponse response,
+            AuthenticationException failed) throws IOException, ServletException {
         SecurityContextHolder.getContext().setAuthentication(null);
 
         if (logger.isDebugEnabled()) {
@@ -246,12 +274,15 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
         return userDetailsService;
     }
 
-    public void setAuthenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest,?> authenticationDetailsSource) {
-        Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
+    public void setAuthenticationDetailsSource(
+            AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
+        Assert.notNull(authenticationDetailsSource,
+                "AuthenticationDetailsSource required");
         this.authenticationDetailsSource = authenticationDetailsSource;
     }
 
-    public void setAuthenticationEntryPoint(DigestAuthenticationEntryPoint authenticationEntryPoint) {
+    public void setAuthenticationEntryPoint(
+            DigestAuthenticationEntryPoint authenticationEntryPoint) {
         this.authenticationEntryPoint = authenticationEntryPoint;
     }
 
@@ -271,18 +302,16 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
         this.userDetailsService = userDetailsService;
     }
 
-
     /**
-     * If you set this property, the Authentication object, which is
-     * created after the successful digest authentication will be marked
-     * as <b>authenticated</b> and filled with the authorities loaded by
-     * the UserDetailsService. It therefore will not be re-authenticated
-     * by your AuthenticationProvider. This means, that only the password
+     * If you set this property, the Authentication object, which is created after the
+     * successful digest authentication will be marked as <b>authenticated</b> and filled
+     * with the authorities loaded by the UserDetailsService. It therefore will not be
+     * re-authenticated by your AuthenticationProvider. This means, that only the password
      * of the user is checked, but not the flags like isEnabled() or
-     * isAccountNonExpired(). You will save some time by enabling this flag,
-     * as otherwise your UserDetailsService will be called twice. A more secure
-     * option would be to introduce a cache around your UserDetailsService, but
-     * if you don't use these flags, you can also safely enable this option.
+     * isAccountNonExpired(). You will save some time by enabling this flag, as otherwise
+     * your UserDetailsService will be called twice. A more secure option would be to
+     * introduce a cache around your UserDetailsService, but if you don't use these flags,
+     * you can also safely enable this option.
      *
      * @param createAuthenticatedToken default is false
      */
@@ -304,8 +333,10 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
 
         DigestData(String header) {
             section212response = header.substring(7);
-            String[] headerEntries = DigestAuthUtils.splitIgnoringQuotes(section212response, ',');
-            Map<String,String> headerMap = DigestAuthUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
+            String[] headerEntries = DigestAuthUtils.splitIgnoringQuotes(
+                    section212response, ',');
+            Map<String, String> headerMap = DigestAuthUtils
+                    .splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
 
             username = headerMap.get("username");
             realm = headerMap.get("realm");
@@ -317,69 +348,87 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
             cnonce = headerMap.get("cnonce"); // RFC 2617 extension
 
             if (logger.isDebugEnabled()) {
-                logger.debug("Extracted username: '" + username + "'; realm: '" + realm + "'; nonce: '"
-                        + nonce + "'; uri: '" + uri + "'; response: '" + response + "'");
+                logger.debug("Extracted username: '" + username + "'; realm: '" + realm
+                        + "'; nonce: '" + nonce + "'; uri: '" + uri + "'; response: '"
+                        + response + "'");
             }
         }
 
-        void validateAndDecode(String entryPointKey, String expectedRealm) throws BadCredentialsException {
+        void validateAndDecode(String entryPointKey, String expectedRealm)
+                throws BadCredentialsException {
             // Check all required parameters were supplied (ie RFC 2069)
-            if ((username == null) || (realm == null) || (nonce == null) || (uri == null) || (response == null)) {
-                throw new BadCredentialsException(messages.getMessage("DigestAuthenticationFilter.missingMandatory",
-                            new Object[]{section212response}, "Missing mandatory digest value; received header {0}"));
+            if ((username == null) || (realm == null) || (nonce == null) || (uri == null)
+                    || (response == null)) {
+                throw new BadCredentialsException(messages.getMessage(
+                        "DigestAuthenticationFilter.missingMandatory",
+                        new Object[] { section212response },
+                        "Missing mandatory digest value; received header {0}"));
             }
             // Check all required parameters for an "auth" qop were supplied (ie RFC 2617)
             if ("auth".equals(qop)) {
                 if ((nc == null) || (cnonce == null)) {
                     if (logger.isDebugEnabled()) {
-                        logger.debug("extracted nc: '" + nc + "'; cnonce: '" + cnonce + "'");
+                        logger.debug("extracted nc: '" + nc + "'; cnonce: '" + cnonce
+                                + "'");
                     }
 
-                    throw new BadCredentialsException(messages.getMessage("DigestAuthenticationFilter.missingAuth",
-                            new Object[]{section212response}, "Missing mandatory digest value; received header {0}"));
+                    throw new BadCredentialsException(messages.getMessage(
+                            "DigestAuthenticationFilter.missingAuth",
+                            new Object[] { section212response },
+                            "Missing mandatory digest value; received header {0}"));
                 }
             }
 
             // Check realm name equals what we expected
             if (!expectedRealm.equals(realm)) {
-                throw new BadCredentialsException(messages.getMessage("DigestAuthenticationFilter.incorrectRealm",
-                            new Object[]{realm, expectedRealm},
-                            "Response realm name '{0}' does not match system realm name of '{1}'"));
+                throw new BadCredentialsException(
+                        messages.getMessage("DigestAuthenticationFilter.incorrectRealm",
+                                new Object[] { realm, expectedRealm },
+                                "Response realm name '{0}' does not match system realm name of '{1}'"));
             }
 
             // Check nonce was Base64 encoded (as sent by DigestAuthenticationEntryPoint)
             if (!Base64.isBase64(nonce.getBytes())) {
-                throw new BadCredentialsException(messages.getMessage("DigestAuthenticationFilter.nonceEncoding",
-                           new Object[]{nonce}, "Nonce is not encoded in Base64; received nonce {0}"));
+                throw new BadCredentialsException(messages.getMessage(
+                        "DigestAuthenticationFilter.nonceEncoding",
+                        new Object[] { nonce },
+                        "Nonce is not encoded in Base64; received nonce {0}"));
             }
 
             // Decode nonce from Base64
             // format of nonce is:
             // base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
             String nonceAsPlainText = new String(Base64.decode(nonce.getBytes()));
-            String[] nonceTokens = StringUtils.delimitedListToStringArray(nonceAsPlainText, ":");
+            String[] nonceTokens = StringUtils.delimitedListToStringArray(
+                    nonceAsPlainText, ":");
 
             if (nonceTokens.length != 2) {
-                throw new BadCredentialsException(messages.getMessage("DigestAuthenticationFilter.nonceNotTwoTokens",
-                                new Object[]{nonceAsPlainText}, "Nonce should have yielded two tokens but was {0}"));
+                throw new BadCredentialsException(messages.getMessage(
+                        "DigestAuthenticationFilter.nonceNotTwoTokens",
+                        new Object[] { nonceAsPlainText },
+                        "Nonce should have yielded two tokens but was {0}"));
             }
 
             // Extract expiry time from nonce
 
             try {
                 nonceExpiryTime = new Long(nonceTokens[0]).longValue();
-            } catch (NumberFormatException nfe) {
-                throw new BadCredentialsException(messages.getMessage("DigestAuthenticationFilter.nonceNotNumeric",
-                                new Object[]{nonceAsPlainText},
+            }
+            catch (NumberFormatException nfe) {
+                throw new BadCredentialsException(
+                        messages.getMessage("DigestAuthenticationFilter.nonceNotNumeric",
+                                new Object[] { nonceAsPlainText },
                                 "Nonce token should have yielded a numeric first token, but was {0}"));
             }
 
             // Check signature of nonce matches this expiry time
-            String expectedNonceSignature = DigestAuthUtils.md5Hex(nonceExpiryTime + ":" + entryPointKey);
+            String expectedNonceSignature = DigestAuthUtils.md5Hex(nonceExpiryTime + ":"
+                    + entryPointKey);
 
             if (!expectedNonceSignature.equals(nonceTokens[1])) {
-                new BadCredentialsException(messages.getMessage("DigestAuthenticationFilter.nonceCompromised",
-                                new Object[]{nonceAsPlainText}, "Nonce token compromised {0}"));
+                new BadCredentialsException(messages.getMessage(
+                        "DigestAuthenticationFilter.nonceCompromised",
+                        new Object[] { nonceAsPlainText }, "Nonce token compromised {0}"));
             }
         }
 
@@ -387,8 +436,8 @@ public class DigestAuthenticationFilter extends GenericFilterBean implements Mes
             // Compute the expected response-digest (will be in hex form)
 
             // Don't catch IllegalArgumentException (already checked validity)
-            return DigestAuthUtils.generateDigest(passwordAlreadyEncoded, username, realm, password,
-                    httpMethod, uri, qop, nonce, nc, cnonce);
+            return DigestAuthUtils.generateDigest(passwordAlreadyEncoded, username,
+                    realm, password, httpMethod, uri, qop, nonce, nc, cnonce);
         }
 
         boolean isNonceExpired() {

+ 200 - 112
web/src/test/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilterTests.java

@@ -15,11 +15,20 @@
 
 package org.springframework.security.web.authentication.www;
 
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.*;
+import static org.fest.assertions.Assertions.*;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import java.io.IOException;
-import java.util.*;
+import java.util.Map;
+
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.ServletException;
@@ -32,7 +41,9 @@ import org.junit.Before;
 import org.junit.Test;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserDetails;
@@ -41,7 +52,6 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.security.core.userdetails.cache.NullUserCache;
 import org.springframework.util.StringUtils;
 
-
 /**
  * Tests {@link DigestAuthenticationFilter}.
  *
@@ -49,7 +59,8 @@ import org.springframework.util.StringUtils;
  * @author Luke Taylor
  */
 public class DigestAuthenticationFilterTests {
-    //~ Static fields/initializers =====================================================================================
+    // ~ Static fields/initializers
+    // =====================================================================================
 
     private static final String NC = "00000002";
     private static final String CNONCE = "c822c727a648aba7";
@@ -65,23 +76,26 @@ public class DigestAuthenticationFilterTests {
      */
     private static final String NONCE = generateNonce(60);
 
-    //~ Instance fields ================================================================================================
+    // ~ Instance fields
+    // ================================================================================================
 
-    //    private ApplicationContext ctx;
+    // private ApplicationContext ctx;
     private DigestAuthenticationFilter filter;
     private MockHttpServletRequest request;
 
+    // ~ Methods
+    // ========================================================================================================
 
-    //~ Methods ========================================================================================================
-
-    private String createAuthorizationHeader(String username, String realm, String nonce, String uri,
-                                             String responseDigest, String qop, String nc, String cnonce) {
-        return "Digest username=\"" + username + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", uri=\"" + uri
-                + "\", response=\"" + responseDigest + "\", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + cnonce + "\"";
+    private String createAuthorizationHeader(String username, String realm, String nonce,
+            String uri, String responseDigest, String qop, String nc, String cnonce) {
+        return "Digest username=\"" + username + "\", realm=\"" + realm + "\", nonce=\""
+                + nonce + "\", uri=\"" + uri + "\", response=\"" + responseDigest
+                + "\", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + cnonce + "\"";
     }
 
-    private MockHttpServletResponse executeFilterInContainerSimulator(Filter filter, final ServletRequest request,
-                                                                      final boolean expectChainToProceed) throws ServletException, IOException {
+    private MockHttpServletResponse executeFilterInContainerSimulator(Filter filter,
+            final ServletRequest request, final boolean expectChainToProceed)
+            throws ServletException, IOException {
         final MockHttpServletResponse response = new MockHttpServletResponse();
 
         final FilterChain chain = mock(FilterChain.class);
@@ -111,8 +125,10 @@ public class DigestAuthenticationFilterTests {
 
         // Create User Details Service
         UserDetailsService uds = new UserDetailsService() {
-            public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
-                return new User("rod,ok", "koala", AuthorityUtils.createAuthorityList("ROLE_ONE","ROLE_TWO"));
+            public UserDetails loadUserByUsername(String username)
+                    throws UsernameNotFoundException {
+                return new User("rod,ok", "koala", AuthorityUtils.createAuthorityList(
+                        "ROLE_ONE", "ROLE_TWO"));
             }
         };
 
@@ -129,25 +145,28 @@ public class DigestAuthenticationFilterTests {
     }
 
     @Test
-    public void testExpiredNonceReturnsForbiddenWithStaleHeader()
-            throws Exception {
+    public void testExpiredNonceReturnsForbiddenWithStaleHeader() throws Exception {
         String nonce = generateNonce(0);
-        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
-                REQUEST_URI, QOP, nonce, NC, CNONCE);
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
+                PASSWORD, "GET", REQUEST_URI, QOP, nonce, NC, CNONCE);
 
-        request.addHeader("Authorization",
-                createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+        request.addHeader(
+                "Authorization",
+                createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI,
+                        responseDigest, QOP, NC, CNONCE));
 
         Thread.sleep(1000); // ensures token expired
 
-        MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
+        MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
+                request, false);
 
         assertNull(SecurityContextHolder.getContext().getAuthentication());
         assertEquals(401, response.getStatus());
 
         String header = response.getHeader("WWW-Authenticate").toString().substring(7);
         String[] headerEntries = StringUtils.commaDelimitedListToStringArray(header);
-        Map<String,String> headerMap = DigestAuthUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
+        Map<String, String> headerMap = DigestAuthUtils
+                .splitEachArrayElementAndCreateMap(headerEntries, "=", "\"");
         assertEquals("true", headerMap.get("stale"));
     }
 
@@ -175,13 +194,14 @@ public class DigestAuthenticationFilterTests {
     }
 
     @Test
-    public void testInvalidDigestAuthorizationTokenGeneratesError()
-            throws Exception {
+    public void testInvalidDigestAuthorizationTokenGeneratesError() throws Exception {
         String token = "NOT_A_VALID_TOKEN_AS_MISSING_COLON";
 
-        request.addHeader("Authorization", "Digest " + new String(Base64.encodeBase64(token.getBytes())));
+        request.addHeader("Authorization",
+                "Digest " + new String(Base64.encodeBase64(token.getBytes())));
 
-        MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
+        MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
+                request, false);
 
         assertEquals(401, response.getStatus());
         assertNull(SecurityContextHolder.getContext().getAuthentication());
@@ -191,7 +211,8 @@ public class DigestAuthenticationFilterTests {
     public void testMalformedHeaderReturnsForbidden() throws Exception {
         request.addHeader("Authorization", "Digest scsdcsdc");
 
-        MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
+        MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
+                request, false);
 
         assertNull(SecurityContextHolder.getContext().getAuthentication());
         assertEquals(401, response.getStatus());
@@ -201,28 +222,36 @@ public class DigestAuthenticationFilterTests {
     public void testNonBase64EncodedNonceReturnsForbidden() throws Exception {
         String nonce = "NOT_BASE_64_ENCODED";
 
-        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
-                REQUEST_URI, QOP, nonce, NC, CNONCE);
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
+                PASSWORD, "GET", REQUEST_URI, QOP, nonce, NC, CNONCE);
 
-        request.addHeader("Authorization",
-                createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+        request.addHeader(
+                "Authorization",
+                createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI,
+                        responseDigest, QOP, NC, CNONCE));
 
-        MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
+        MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
+                request, false);
 
         assertNull(SecurityContextHolder.getContext().getAuthentication());
         assertEquals(401, response.getStatus());
     }
 
     @Test
-    public void testNonceWithIncorrectSignatureForNumericFieldReturnsForbidden() throws Exception {
-        String nonce = new String(Base64.encodeBase64("123456:incorrectStringPassword".getBytes()));
-        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
-                REQUEST_URI, QOP, nonce, NC, CNONCE);
+    public void testNonceWithIncorrectSignatureForNumericFieldReturnsForbidden()
+            throws Exception {
+        String nonce = new String(Base64.encodeBase64("123456:incorrectStringPassword"
+                .getBytes()));
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
+                PASSWORD, "GET", REQUEST_URI, QOP, nonce, NC, CNONCE);
 
-        request.addHeader("Authorization",
-                createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+        request.addHeader(
+                "Authorization",
+                createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI,
+                        responseDigest, QOP, NC, CNONCE));
 
-        MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
+        MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
+                request, false);
 
         assertNull(SecurityContextHolder.getContext().getAuthentication());
         assertEquals(401, response.getStatus());
@@ -230,29 +259,38 @@ public class DigestAuthenticationFilterTests {
 
     @Test
     public void testNonceWithNonNumericFirstElementReturnsForbidden() throws Exception {
-        String nonce = new String(Base64.encodeBase64("hello:ignoredSecondElement".getBytes()));
-        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
-                REQUEST_URI, QOP, nonce, NC, CNONCE);
+        String nonce = new String(Base64.encodeBase64("hello:ignoredSecondElement"
+                .getBytes()));
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
+                PASSWORD, "GET", REQUEST_URI, QOP, nonce, NC, CNONCE);
 
-        request.addHeader("Authorization",
-                createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+        request.addHeader(
+                "Authorization",
+                createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI,
+                        responseDigest, QOP, NC, CNONCE));
 
-        MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
+        MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
+                request, false);
 
         assertNull(SecurityContextHolder.getContext().getAuthentication());
         assertEquals(401, response.getStatus());
     }
 
     @Test
-    public void testNonceWithoutTwoColonSeparatedElementsReturnsForbidden() throws Exception {
-        String nonce = new String(Base64.encodeBase64("a base 64 string without a colon".getBytes()));
-        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
-                REQUEST_URI, QOP, nonce, NC, CNONCE);
+    public void testNonceWithoutTwoColonSeparatedElementsReturnsForbidden()
+            throws Exception {
+        String nonce = new String(Base64.encodeBase64("a base 64 string without a colon"
+                .getBytes()));
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
+                PASSWORD, "GET", REQUEST_URI, QOP, nonce, NC, CNONCE);
 
-        request.addHeader("Authorization",
-                createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+        request.addHeader(
+                "Authorization",
+                createAuthorizationHeader(USERNAME, REALM, nonce, REQUEST_URI,
+                        responseDigest, QOP, NC, CNONCE));
 
-        MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
+        MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
+                request, false);
 
         assertNull(SecurityContextHolder.getContext().getAuthentication());
         assertEquals(401, response.getStatus());
@@ -260,58 +298,67 @@ public class DigestAuthenticationFilterTests {
 
     @Test
     public void testNormalOperationWhenPasswordIsAlreadyEncoded() throws Exception {
-        String encodedPassword = DigestAuthUtils.encodePasswordInA1Format(USERNAME, REALM, PASSWORD);
-        String responseDigest = DigestAuthUtils.generateDigest(true, USERNAME, REALM, encodedPassword, "GET",
-                REQUEST_URI, QOP, NONCE, NC, CNONCE);
+        String encodedPassword = DigestAuthUtils.encodePasswordInA1Format(USERNAME,
+                REALM, PASSWORD);
+        String responseDigest = DigestAuthUtils.generateDigest(true, USERNAME, REALM,
+                encodedPassword, "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
 
-        request.addHeader("Authorization",
-                createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+        request.addHeader(
+                "Authorization",
+                createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI,
+                        responseDigest, QOP, NC, CNONCE));
 
         executeFilterInContainerSimulator(filter, request, true);
 
         assertNotNull(SecurityContextHolder.getContext().getAuthentication());
-        assertEquals(USERNAME,
-                ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername());
+        assertEquals(USERNAME, ((UserDetails) SecurityContextHolder.getContext()
+                .getAuthentication().getPrincipal()).getUsername());
     }
 
     @Test
     public void testNormalOperationWhenPasswordNotAlreadyEncoded() throws Exception {
-        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
-                REQUEST_URI, QOP, NONCE, NC, CNONCE);
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
+                PASSWORD, "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
 
-        request.addHeader("Authorization",
-                createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+        request.addHeader(
+                "Authorization",
+                createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI,
+                        responseDigest, QOP, NC, CNONCE));
 
         executeFilterInContainerSimulator(filter, request, true);
 
         assertNotNull(SecurityContextHolder.getContext().getAuthentication());
-        assertEquals(USERNAME,
-                ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername());
-        assertFalse(SecurityContextHolder.getContext().getAuthentication().isAuthenticated());
+        assertEquals(USERNAME, ((UserDetails) SecurityContextHolder.getContext()
+                .getAuthentication().getPrincipal()).getUsername());
+        assertFalse(SecurityContextHolder.getContext().getAuthentication()
+                .isAuthenticated());
     }
 
     @Test
-    public void testNormalOperationWhenPasswordNotAlreadyEncodedAndWithoutReAuthentication() throws Exception {
-        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
-                REQUEST_URI, QOP, NONCE, NC, CNONCE);
+    public void testNormalOperationWhenPasswordNotAlreadyEncodedAndWithoutReAuthentication()
+            throws Exception {
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
+                PASSWORD, "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
 
-        request.addHeader("Authorization",
-                createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+        request.addHeader(
+                "Authorization",
+                createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI,
+                        responseDigest, QOP, NC, CNONCE));
 
         filter.setCreateAuthenticatedToken(true);
         executeFilterInContainerSimulator(filter, request, true);
 
         assertNotNull(SecurityContextHolder.getContext().getAuthentication());
-        assertEquals(USERNAME,
-                ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername());
-        assertTrue(SecurityContextHolder.getContext().getAuthentication().isAuthenticated());
-        assertEquals(AuthorityUtils.createAuthorityList("ROLE_ONE","ROLE_TWO"),
+        assertEquals(USERNAME, ((UserDetails) SecurityContextHolder.getContext()
+                .getAuthentication().getPrincipal()).getUsername());
+        assertTrue(SecurityContextHolder.getContext().getAuthentication()
+                .isAuthenticated());
+        assertEquals(AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"),
                 SecurityContextHolder.getContext().getAuthentication().getAuthorities());
     }
 
     @Test
-    public void otherAuthorizationSchemeIsIgnored()
-            throws Exception {
+    public void otherAuthorizationSchemeIsIgnored() throws Exception {
         request.addHeader("Authorization", "SOME_OTHER_AUTHENTICATION_SCHEME");
 
         executeFilterInContainerSimulator(filter, request, true);
@@ -319,14 +366,14 @@ public class DigestAuthenticationFilterTests {
         assertNull(SecurityContextHolder.getContext().getAuthentication());
     }
 
-    @Test(expected=IllegalArgumentException.class)
+    @Test(expected = IllegalArgumentException.class)
     public void startupDetectsMissingAuthenticationEntryPoint() throws Exception {
         DigestAuthenticationFilter filter = new DigestAuthenticationFilter();
         filter.setUserDetailsService(mock(UserDetailsService.class));
         filter.afterPropertiesSet();
     }
 
-    @Test(expected=IllegalArgumentException.class)
+    @Test(expected = IllegalArgumentException.class)
     public void startupDetectsMissingUserDetailsService() throws Exception {
         DigestAuthenticationFilter filter = new DigestAuthenticationFilter();
         filter.setAuthenticationEntryPoint(new DigestAuthenticationEntryPoint());
@@ -334,26 +381,32 @@ public class DigestAuthenticationFilterTests {
     }
 
     @Test
-    public void successfulLoginThenFailedLoginResultsInSessionLosingToken() throws Exception {
-        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
-                REQUEST_URI, QOP, NONCE, NC, CNONCE);
+    public void successfulLoginThenFailedLoginResultsInSessionLosingToken()
+            throws Exception {
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
+                PASSWORD, "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
 
-        request.addHeader("Authorization",
-                createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+        request.addHeader(
+                "Authorization",
+                createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI,
+                        responseDigest, QOP, NC, CNONCE));
 
         executeFilterInContainerSimulator(filter, request, true);
 
         assertNotNull(SecurityContextHolder.getContext().getAuthentication());
 
         // Now retry, giving an invalid nonce
-        responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, "WRONG_PASSWORD", "GET",
-                REQUEST_URI, QOP, NONCE, NC, CNONCE);
+        responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
+                "WRONG_PASSWORD", "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
 
         request = new MockHttpServletRequest();
-        request.addHeader("Authorization",
-                createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+        request.addHeader(
+                "Authorization",
+                createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI,
+                        responseDigest, QOP, NC, CNONCE));
 
-        MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
+        MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
+                request, false);
 
         // Check we lost our previous authentication
         assertNull(SecurityContextHolder.getContext().getAuthentication());
@@ -364,13 +417,16 @@ public class DigestAuthenticationFilterTests {
     public void wrongCnonceBasedOnDigestReturnsForbidden() throws Exception {
         String cnonce = "NOT_SAME_AS_USED_FOR_DIGEST_COMPUTATION";
 
-        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, PASSWORD, "GET",
-                REQUEST_URI, QOP, NONCE, NC, "DIFFERENT_CNONCE");
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
+                PASSWORD, "GET", REQUEST_URI, QOP, NONCE, NC, "DIFFERENT_CNONCE");
 
-        request.addHeader("Authorization",
-                createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, cnonce));
+        request.addHeader(
+                "Authorization",
+                createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI,
+                        responseDigest, QOP, NC, cnonce));
 
-        MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
+        MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
+                request, false);
 
         assertNull(SecurityContextHolder.getContext().getAuthentication());
         assertEquals(401, response.getStatus());
@@ -379,13 +435,16 @@ public class DigestAuthenticationFilterTests {
     @Test
     public void wrongDigestReturnsForbidden() throws Exception {
         String password = "WRONG_PASSWORD";
-        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM, password, "GET",
-                REQUEST_URI, QOP, NONCE, NC, CNONCE);
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
+                password, "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
 
-        request.addHeader("Authorization",
-                createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+        request.addHeader(
+                "Authorization",
+                createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI,
+                        responseDigest, QOP, NC, CNONCE));
 
-        MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
+        MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
+                request, false);
 
         assertNull(SecurityContextHolder.getContext().getAuthentication());
         assertEquals(401, response.getStatus());
@@ -394,13 +453,16 @@ public class DigestAuthenticationFilterTests {
     @Test
     public void wrongRealmReturnsForbidden() throws Exception {
         String realm = "WRONG_REALM";
-        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, realm, PASSWORD, "GET",
-                REQUEST_URI, QOP, NONCE, NC, CNONCE);
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, realm,
+                PASSWORD, "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
 
-        request.addHeader("Authorization",
-                createAuthorizationHeader(USERNAME, realm, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+        request.addHeader(
+                "Authorization",
+                createAuthorizationHeader(USERNAME, realm, NONCE, REQUEST_URI,
+                        responseDigest, QOP, NC, CNONCE));
 
-        MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
+        MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
+                request, false);
 
         assertNull(SecurityContextHolder.getContext().getAuthentication());
         assertEquals(401, response.getStatus());
@@ -408,15 +470,41 @@ public class DigestAuthenticationFilterTests {
 
     @Test
     public void wrongUsernameReturnsForbidden() throws Exception {
-        String responseDigest = DigestAuthUtils.generateDigest(false, "NOT_A_KNOWN_USER", REALM, PASSWORD,
-                "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
+        String responseDigest = DigestAuthUtils.generateDigest(false, "NOT_A_KNOWN_USER",
+                REALM, PASSWORD, "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
 
-        request.addHeader("Authorization",
-                createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI, responseDigest, QOP, NC, CNONCE));
+        request.addHeader(
+                "Authorization",
+                createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI,
+                        responseDigest, QOP, NC, CNONCE));
 
-        MockHttpServletResponse response = executeFilterInContainerSimulator(filter, request, false);
+        MockHttpServletResponse response = executeFilterInContainerSimulator(filter,
+                request, false);
 
         assertNull(SecurityContextHolder.getContext().getAuthentication());
         assertEquals(401, response.getStatus());
     }
+
+    // SEC-3108
+    @Test
+    public void authenticationCreatesEmptyContext() throws Exception {
+        SecurityContext existingContext = SecurityContextHolder.createEmptyContext();
+        TestingAuthenticationToken existingAuthentication = new TestingAuthenticationToken("existingauthenitcated", "pass", "ROLE_USER");
+        existingContext.setAuthentication(existingAuthentication);
+
+        SecurityContextHolder.setContext(existingContext);
+
+        String responseDigest = DigestAuthUtils.generateDigest(false, USERNAME, REALM,
+                PASSWORD, "GET", REQUEST_URI, QOP, NONCE, NC, CNONCE);
+
+        request.addHeader(
+                "Authorization",
+                createAuthorizationHeader(USERNAME, REALM, NONCE, REQUEST_URI,
+                        responseDigest, QOP, NC, CNONCE));
+
+        filter.setCreateAuthenticatedToken(true);
+        executeFilterInContainerSimulator(filter, request, true);
+
+        assertThat(existingAuthentication).isSameAs(existingContext.getAuthentication());
+    }
 }