diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java index ced37900d2..a5e0e19be5 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java +++ b/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 * SecurityContextHolder. *

- * For a detailed background on what this filter is designed to process, refer to - * RFC 2617 (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 RFC 2617 (which superseded RFC 2069, + * although this filter support clients that implement either RFC 2617 or RFC 2069). *

- * 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). *

- * 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}. *

- * If authentication is successful, the resulting {@link org.springframework.security.core.Authentication Authentication} - * object will be placed into the SecurityContextHolder. + * If authentication is successful, the resulting + * {@link org.springframework.security.core.Authentication Authentication} object will be + * placed into the SecurityContextHolder. *

- * 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. *

- * 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 authenticationDetailsSource = new WebAuthenticationDetailsSource(); + private AuthenticationDetailsSource 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 authenticationDetailsSource) { - Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required"); + public void setAuthenticationDetailsSource( + AuthenticationDetailsSource 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 authenticated 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 authenticated 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 headerMap = DigestAuthUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\""); + String[] headerEntries = DigestAuthUtils.splitIgnoringQuotes( + section212response, ','); + Map 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() { diff --git a/web/src/test/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilterTests.java b/web/src/test/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilterTests.java index 1d79c11f8b..ef7265310c 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilterTests.java +++ b/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 headerMap = DigestAuthUtils.splitEachArrayElementAndCreateMap(headerEntries, "=", "\""); + Map 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()); + } }