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 forView on GitHub (pinned to 96852e8860)
Solutions
- Confirm the servicePrincipal matches the SPN the client used and that the keytab contains that principal's key (kvno must match).
- Decode the Authorization header correctly: strip 'Negotiate ' prefix and Base64-decode before passing the token to validateTicket.
- 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).
- Ensure krb5.conf exists and clocks are synchronized; expired or replayed tickets are a common cause.
- 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
- Always strip the 'Negotiate ' prefix and Base64-decode before validating.
- Keep keytab KVNO in sync with the KDC after password rotations.
- Register exactly one SPN per service account to avoid duplicate-SPN ticket failures.
- Check e.getCause() to distinguish JAAS config problems from token problems.
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
- GSSContext name of the context initiator is null
- Error running rest call
- ticketValidator must be set
- userDetailsService must be set
- Unauthenticated or no response token
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/463f8a9007128d11.
Report an issue: GitHub.