spring-projects/spring-security · error · BadCredentialsException

DigestAuthenticationFilter.incorrectRealm

DigestAuthenticationFilter.incorrectRealm

Error message

Response realm name '{0}' does not match system realm name of '{1}'

What it means

validateAndDecode compares the realm sent by the client in the Digest header against the realm expected by the server (from DigestAuthenticationEntryPoint). A mismatch throws this BadCredentialsException listing both realms ({0} = client realm, {1} = expected realm), since a correct digest client echoes back the server's realm and the digest hash depends on it.

Solutions

  1. Read both realm values from the exception message and align them: change the client to use the server's realm, or update DigestAuthenticationEntryPoint's realmName to the expected one.
  2. Prefer having the client take realm/nonce dynamically from the server's WWW-Authenticate challenge instead of hardcoding.
  3. Grep client code/config for the old realm string and update it after any realm rename.
  4. If two apps share a host/path space, give each a distinct path or use the same realm intentionally.

Example fix

// before (server config)
http.exceptionHandling().authenticationEntryPoint(new DigestAuthenticationEntryPoint()); // default/wrong realm
// after
DigestAuthenticationEntryPoint entryPoint = new DigestAuthenticationEntryPoint();
entryPoint.setRealmName("MyAppRealm"); // must match the realm the clients echo back
Defensive patterns

Strategy: validation

Validate before calling

String serverRealm = extractRealm(conn.getHeaderField("WWW-Authenticate"));
if (!serverRealm.equals(clientConfiguredRealm)) {
    clientConfiguredRealm = serverRealm; // use the realm the server advertises
}

Try / catch

try {
    chain.doFilter(request, response);
} catch (BadCredentialsException e) {
    if (e.getMessage().contains("does not match system realm name")) {
        // parse both realms from the message and re-authenticate with the server's realm
        response.sendError(401, "Realm mismatch, restart auth with server challenge");
    }
}

Prevention

When it happens

Trigger: A request's Digest header contains realm="X" while the server's entry point is configured with realm="Y" (the received and expected values are both in the message). E.g. client hardcodes a realm string, or the server realm was renamed.

Common situations: Multiple server environments (dev/prod) with different realm names but a shared client; a server-side Spring Security config change renaming the realm; hardcoded realm in scripts/mobile apps; reverse proxy fronting two apps with different realms on the same host.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/75774e30e6263380. Report an issue: GitHub.

Appendix: source

Thrown at web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java:375

			// Check all required parameters were supplied (ie RFC 2069)
			if ((this.username == null) || (this.realm == null) || (this.nonce == null) || (this.uri == null)
					|| (this.response == null)) {
				throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage(
						"DigestAuthenticationFilter.missingMandatory", new Object[] { this.section212response },
						"Missing mandatory digest value; received header {0}"));
			}
			// Check all required parameters for an "auth" qop were supplied (ie RFC 2617)
			if ("auth".equals(this.qop)) {
				if ((this.nc == null) || (this.cnonce == null)) {
					logger.debug(LogMessage.format("extracted nc: '%s'; cnonce: '%s'", this.nc, this.cnonce));
					throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage(
							"DigestAuthenticationFilter.missingAuth", new Object[] { this.section212response },
							"Missing mandatory digest value; received header {0}"));
				}
			}
			// Check realm name equals what we expected
			if (!this.realm.equals(expectedRealm)) {
				throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage(
						"DigestAuthenticationFilter.incorrectRealm", new Object[] { this.realm, expectedRealm },
						"Response realm name '{0}' does not match system realm name of '{1}'"));
			}
			// Check nonce was Base64 encoded (as sent by DigestAuthenticationEntryPoint)
			final byte[] nonceBytes;
			try {
				nonceBytes = Base64.getDecoder().decode(this.nonce.getBytes());
			}
			catch (IllegalArgumentException ex) {
				throw new BadCredentialsException(
						DigestAuthenticationFilter.this.messages.getMessage("DigestAuthenticationFilter.nonceEncoding",
								new Object[] { this.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(nonceBytes);
			String[] nonceTokens = StringUtils.delimitedListToStringArray(nonceAsPlainText, ":");
			if (nonceTokens.length != 2) {

View on GitHub (pinned to 96852e8860)