{"record":{"id":"463f8a9007128d11","repo":"spring-projects/spring-security","slug":"kerberos-validation-not-successful","errorCode":null,"errorMessage":"Kerberos validation not successful","messagePattern":"Kerberos validation not successful","errorType":"exception","errorClass":"BadCredentialsException","httpStatus":null,"severity":"error","filePath":"kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosTicketValidator.java","lineNumber":97,"sourceCode":"\n\t@Override\n\tpublic KerberosTicketValidation validateTicket(byte[] token) {\n\t\ttry {\n\t\t\tif (this.serviceSubject == null) {\n\t\t\t\tthrow new IllegalStateException(\"serviceSubject must be initialized\");\n\t\t\t}\n\t\t\tif (!this.multiTier) {\n\t\t\t\treturn Subject.doAs(this.serviceSubject, new KerberosValidateAction(token));\n\t\t\t}\n\n\t\t\tSubject subjectCopy = JaasUtil.copySubject(this.serviceSubject);\n\t\t\tJaasSubjectHolder subjectHolder = new JaasSubjectHolder(subjectCopy);\n\n\t\t\treturn Subject.doAs(subjectHolder.getJaasSubject(), new KerberosMultitierValidateAction(token));\n\n\t\t}\n\t\tcatch (IllegalStateException | PrivilegedActionException ex) {\n\t\t\tthrow new BadCredentialsException(\"Kerberos validation not successful\", ex);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void afterPropertiesSet() throws Exception {\n\t\tAssert.notNull(this.servicePrincipal, \"servicePrincipal must be specified\");\n\t\tAssert.notNull(this.keyTabLocation, \"keyTab must be specified\");\n\t\tif (this.servicePrincipal == null || this.keyTabLocation == null) {\n\t\t\tthrow new IllegalStateException(\"servicePrincipal and keyTabLocation must be set\");\n\t\t}\n\t\tif (this.keyTabLocation instanceof ClassPathResource) {\n\t\t\tthis.LOG.warn(\n\t\t\t\t\t\"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.\");\n\t\t}\n\t\tString keyTabLocationAsString = this.keyTabLocation.getURL().toExternalForm();\n\t\t// We need to remove the file prefix (if there is one), as it is not supported in\n\t\t// Java 7 anymore.\n\t\t// As Java 6 accepts it with and without the prefix, we don't need to check for","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/kerberos/kerberos-core/src/main/java/org/springframework/security/kerberos/authentication/sun/SunJaasKerberosTicketValidator.java#L79-L115","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nString header = request.getHeader(\"Authorization\"); // \"Negotiate <token>\"\nbyte[] token = header.getBytes(); // wrong: raw string\nvalidator.validateTicket(token);\n\n// after\nString header = request.getHeader(\"Authorization\");\nString b64 = header.substring(header.indexOf(' ') + 1);\nbyte[] token = Base64.getDecoder().decode(b64);\nvalidator.validateTicket(token);","handlingStrategy":"validation","validationCode":"// before validateTicket\nif (token == null || token.length == 0) throw new BadCredentialsException(\"missing spnego token\");\nString auth = request.getHeader(\"Authorization\");\nif (auth == null || !auth.startsWith(\"Negotiate \")) throw new BadCredentialsException(\"no negotiate header\");\nbyte[] token = Base64.getDecoder().decode(auth.substring(\"Negotiate \".length()));","typeGuard":null,"tryCatchPattern":"try {\n    return validator.validateTicket(token);\n} catch (BadCredentialsException e) {\n    LOGGER.debug(\"ticket validation failed, cause: {}\", e.getCause(), e);\n    response.setStatus(401);\n    response.setHeader(\"WWW-Authenticate\", \"Negotiate\");\n    return null;\n}","preventionTips":["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."],"tags":["kerberos","spnego","gss","ticket-validation"],"backgroundTag":"authentication-required","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}