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

This commit is contained in:
Rob Winch 2015-10-27 14:00:02 -05:00
parent 90f230cbfa
commit c64b80564e
2 changed files with 337 additions and 200 deletions

View File

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

View File

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