spring-projects/spring-security · error · BadCredentialsException

Kerberos validation not successful

Error message

Kerberos validation not successful

What it means

SunJaasKerberosTicketValidator.validateTicket() wraps IllegalStateException or PrivilegedActionException (from the JAAS/GSS validation action) in a BadCredentialsException. It means the Spnego/Kerberos service ticket presented by the client could not be validated against the service principal, e.g. the underlying JAAS login failed or the GSS acceptSecContext step threw.

Source

Thrown at kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosTicketValidator.java:97

	@Override
	public KerberosTicketValidation validateTicket(byte[] token) {
		try {
			if (this.serviceSubject == null) {
				throw new IllegalStateException("serviceSubject must be initialized");
			}
			if (!this.multiTier) {
				return Subject.doAs(this.serviceSubject, new KerberosValidateAction(token));
			}

			Subject subjectCopy = JaasUtil.copySubject(this.serviceSubject);
			JaasSubjectHolder subjectHolder = new JaasSubjectHolder(subjectCopy);

			return Subject.doAs(subjectHolder.getJaasSubject(), new KerberosMultitierValidateAction(token));

		}
		catch (IllegalStateException | PrivilegedActionException ex) {
			throw new BadCredentialsException("Kerberos validation not successful", ex);
		}
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		Assert.notNull(this.servicePrincipal, "servicePrincipal must be specified");
		Assert.notNull(this.keyTabLocation, "keyTab must be specified");
		if (this.servicePrincipal == null || this.keyTabLocation == null) {
			throw new IllegalStateException("servicePrincipal and keyTabLocation must be set");
		}
		if (this.keyTabLocation instanceof ClassPathResource) {
			this.LOG.warn(
					"Your keytab is in the classpath. This file needs special protection and shouldn't be in the classpath. JAAS may also not be able to load this file from classpath.");
		}
		String keyTabLocationAsString = this.keyTabLocation.getURL().toExternalForm();
		// We need to remove the file prefix (if there is one), as it is not supported in
		// Java 7 anymore.
		// As Java 6 accepts it with and without the prefix, we don't need to check for

View on GitHub (pinned to 96852e8860)

Solutions

  1. Confirm the servicePrincipal matches the SPN the client used and that the keytab contains that principal's key (kvno must match).
  2. Decode the Authorization header correctly: strip 'Negotiate ' prefix and Base64-decode before passing the token to validateTicket.
  3. Set the system property sun.security.krb5.debug=true and check the wrapped cause for GSS error codes (e.g. KRB_AP_ERR_TKT_EXPIRED).
  4. Ensure krb5.conf exists and clocks are synchronized; expired or replayed tickets are a common cause.
  5. Check the wrapped exception (BadCredentialsException.getCause()) to distinguish JAAS login failures from GSS token failures.

Example fix

// before
String header = request.getHeader("Authorization"); // "Negotiate <token>"
byte[] token = header.getBytes(); // wrong: raw string
validator.validateTicket(token);

// after
String header = request.getHeader("Authorization");
String b64 = header.substring(header.indexOf(' ') + 1);
byte[] token = Base64.getDecoder().decode(b64);
validator.validateTicket(token);
Defensive patterns

Strategy: validation

Validate before calling

// before validateTicket
if (token == null || token.length == 0) throw new BadCredentialsException("missing spnego token");
String auth = request.getHeader("Authorization");
if (auth == null || !auth.startsWith("Negotiate ")) throw new BadCredentialsException("no negotiate header");
byte[] token = Base64.getDecoder().decode(auth.substring("Negotiate ".length()));

Try / catch

try {
    return validator.validateTicket(token);
} catch (BadCredentialsException e) {
    LOGGER.debug("ticket validation failed, cause: {}", e.getCause(), e);
    response.setStatus(401);
    response.setHeader("WWW-Authenticate", "Negotiate");
    return null;
}

Prevention

When it happens

Trigger: Calling validateTicket(byte[] token) with a Base64-decoded Spnego/Negotiate token whose GSS validation fails: token expired/replayed, token encrypted for a different service principal, servicePrincipal/keytab not set, or the internal KerberosValidateAction throwing PrivilegedActionException; also thrown when Subject.doAs raises IllegalStateException.

Common situations: Client tickets issued by a realm the server doesn't trust, servicePrincipalName (SPN) not registered or duplicated in AD, missing krb5.conf/keytab on the server, or token mangling caused by incorrect Base64 decoding of the Authorization header.

Related errors


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