+ "'; uri: '" + username + "'; response: '" + username
+ "'");
}
fail(request, response,
new BadCredentialsException(messages.getMessage(
"DigestProcessingFilter.missingMandatory",
new Object[] {section212response},
"Missing mandatory digest value; received header {0}")));
return;
}
// 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 + "'");
}
fail(request, response,
new BadCredentialsException(messages.getMessage(
"DigestProcessingFilter.missingAuth",
new Object[] {section212response},
"Missing mandatory digest value; received header {0}")));
return;
}
}
// Check realm name equals what we expected
if (!this.getAuthenticationEntryPoint().getRealmName().equals(realm)) {
fail(request, response,
new BadCredentialsException(messages.getMessage(
"DigestProcessingFilter.incorrectRealm",
new Object[] {realm, this.getAuthenticationEntryPoint()
.getRealmName()},
"Response realm name '{0}' does not match system realm name of '{1}'")));
return;
}
// Check nonce was a Base64 encoded (as sent by DigestProcessingFilterEntryPoint)
if (!Base64.isArrayByteBase64(nonce.getBytes())) {
fail(request, response,
new BadCredentialsException(messages.getMessage(
"DigestProcessingFilter.nonceEncoding",
new Object[] {nonce},
"Nonce is not encoded in Base64; received nonce {0}")));
return;
}
// Decode nonce from Base64
// format of nonce is:
// base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
String nonceAsPlainText = new String(Base64.decodeBase64(
nonce.getBytes()));
String[] nonceTokens = StringUtils.delimitedListToStringArray(nonceAsPlainText,
":");
if (nonceTokens.length != 2) {
fail(request, response,
new BadCredentialsException(messages.getMessage(
"DigestProcessingFilter.nonceNotTwoTokens",
new Object[] {nonceAsPlainText},
"Nonce should have yielded two tokens but was {0}")));
return;
}
// Extract expiry time from nonce
long nonceExpiryTime;
try {
nonceExpiryTime = new Long(nonceTokens[0]).longValue();
} catch (NumberFormatException nfe) {
fail(request, response,
new BadCredentialsException(messages.getMessage(
"DigestProcessingFilter.nonceNotNumeric",
new Object[] {nonceAsPlainText},
"Nonce token should have yielded a numeric first token, but was {0}")));
return;
}
// Check signature of nonce matches this expiry time
String expectedNonceSignature = DigestUtils.md5Hex(nonceExpiryTime
+ ":" + this.getAuthenticationEntryPoint().getKey());
if (!expectedNonceSignature.equals(nonceTokens[1])) {
fail(request, response,
new BadCredentialsException(messages.getMessage(
"DigestProcessingFilter.nonceCompromised",
new Object[] {nonceAsPlainText},
"Nonce token compromised {0}")));
return;
}
// Lookup password for presented username
// NB: DAO-provided password MUST be clear text - not encoded/salted
// (unless this instance's passwordAlreadyEncoded property is 'false')
boolean loadedFromDao = false;
UserDetails user = userCache.getUserFromCache(username);
if (user == null) {
loadedFromDao = true;
try {
user = userDetailsService.loadUserByUsername(username);
} catch (UsernameNotFoundException notFound) {
fail(request, response,
new BadCredentialsException(messages.getMessage(
"DigestProcessingFilter.usernameNotFound",
new Object[] {username},
"Username {0} not found")));
return;
}
if (user == null) {
throw new AuthenticationServiceException(
"AuthenticationDao returned null, which is an interface contract violation");
}
userCache.putUserInCache(user);
}
// Compute the expected response-digest (will be in hex form)
String serverDigestMd5;
// Don't catch IllegalArgumentException (already checked validity)
serverDigestMd5 = generateDigest(passwordAlreadyEncoded, username,
realm, user.getPassword(),
((HttpServletRequest) request).getMethod(), uri, qop,
nonce, nc, cnonce);
// If digest is incorrect, try refreshing from backend and recomputing
if (!serverDigestMd5.equals(responseDigest) && !loadedFromDao) {
if (logger.isDebugEnabled()) {
logger.debug(
"Digest comparison failure; trying to refresh user from DAO in case password had changed");
}
try {
user = userDetailsService.loadUserByUsername(username);
} catch (UsernameNotFoundException notFound) {
// Would very rarely happen, as user existed earlier
fail(request, response,
new BadCredentialsException(messages.getMessage(
"DigestProcessingFilter.usernameNotFound",
new Object[] {username},
"Username {0} not found")));
}
userCache.putUserInCache(user);
// Don't catch IllegalArgumentException (already checked validity)
serverDigestMd5 = generateDigest(passwordAlreadyEncoded,
username, realm, user.getPassword(),
((HttpServletRequest) request).getMethod(), uri, qop,
nonce, nc, cnonce);
}
// If digest is still incorrect, definitely reject authentication attempt
if (!serverDigestMd5.equals(responseDigest)) {
if (logger.isDebugEnabled()) {
logger.debug("Expected response: '" + serverDigestMd5
+ "' but received: '" + responseDigest
+ "'; is AuthenticationDao returning clear text passwords?");
}
fail(request, response,
new BadCredentialsException(messages.getMessage(
"DigestProcessingFilter.incorrectResponse",
"Incorrect response")));
return;
}