spring-projects/spring-security · error · BadCredentialsException

GSSContext name of the context initiator is null

Error message

GSSContext name of the context initiator is null

What it means

Inside the KerberosValidateAction.run() executed via Subject.doAs, after acceptSecContext processes the ticket, context.getSrcName() returned null, meaning GSS could not determine the initiator's identity. This BadCredentialsException indicates the ticket bytes were not a valid/complete GSS context token.

Source

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

	 */
	private final class KerberosValidateAction implements PrivilegedExceptionAction<KerberosTicketValidation> {

		byte[] kerberosTicket;

		private KerberosValidateAction(byte[] kerberosTicket) {
			this.kerberosTicket = kerberosTicket;
		}

		@Override
		public KerberosTicketValidation run() throws Exception {
			byte[] responseToken = new byte[0];
			GSSName gssName = null;
			GSSContext context = GSSManager.getInstance().createContext((GSSCredential) null);
			while (!context.isEstablished()) {
				responseToken = context.acceptSecContext(this.kerberosTicket, 0, this.kerberosTicket.length);
				gssName = context.getSrcName();
				if (gssName == null) {
					throw new BadCredentialsException("GSSContext name of the context initiator is null");
				}
			}

			GSSCredential delegationCredential = null;
			if (context.getCredDelegState()) {
				delegationCredential = context.getDelegCred();
			}

			if (!SunJaasKerberosTicketValidator.this.holdOnToGSSContext) {
				context.dispose();
			}
			if (gssName == null) {
				throw new BadCredentialsException("GSSContext name of the context initiator is null");
			}
			String servicePrincipal = SunJaasKerberosTicketValidator.this.servicePrincipal;
			if (servicePrincipal == null) {
				throw new IllegalStateException("servicePrincipal must be set");
			}

View on GitHub (pinned to 96852e8860)

Solutions

  1. Verify the token is Base64-decoded and includes the full SPNEGO wrapper; log token length before validation.
  2. Handle the initial empty/first-leg SPNEGO challenge separately instead of passing an empty token to validateTicket.
  3. Check intermediate proxies/load balancers are not stripping or rewriting the Authorization header.
  4. Confirm the browser/client actually obtained a Kerberos (not NTLM) token; check client-side SPN/realm configuration.
  5. Enable sun.security.krb5.debug and JGSS debugging (-Dsun.security.jgss.debug=true) to see where token parsing fails.

Example fix

// before
byte[] ticket = authHeader.getBytes(); // "Negotiate YII..." as raw bytes
validator.validateTicket(ticket);

// after
if (!authHeader.startsWith("Negotiate ")) throw new BadCredentialsException("no kerberos token");
byte[] ticket = Base64.getDecoder().decode(authHeader.substring("Negotiate ".length()));
if (ticket.length == 0) throw new BadCredentialsException("empty token");
validator.validateTicket(ticket);
Defensive patterns

Strategy: validation

Validate before calling

// reject non-kerberos or empty tokens before validation
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Negotiate ")) throw new BadCredentialsException("not a kerberos token");
byte[] token = Base64.getDecoder().decode(header.substring(10));
if (token.length < 10) throw new BadCredentialsException("token too short");

Type guard

static boolean isPlausibleSpnegoToken(byte[] t) {
    // SPNEGO AbstractSyntaxNotification tag or NTLMSSP signature check
    if (t == null || t.length < 8) return false;
    boolean ntlm = t.length > 7 && t[0]=='N' && t[1]=='T' && t[2]=='L' && t[3]=='M';
    return !ntlm;
}

Try / catch

try {
    KerberosTicketValidation v = validator.validateTicket(token);
} catch (BadCredentialsException e) {
    LOGGER.warn("unparseable GSS token (len={})", token.length);
    response.setHeader("WWW-Authenticate", "Negotiate");
    response.sendError(401);
}

Prevention

When it happens

Trigger: The kerberosTicket byte array passed to acceptSecContext is malformed, empty, truncated, or not a valid SPNEGO/Kerberos AP-REQ token, so the context never establishes an initiator name; the loop then throws this exception.

Common situations: Sending the raw 'Authorization: Negotiate xxx' header value without Base64 decoding, headers stripped or modified by proxies/load balancers, browser sending NTLM instead of Kerberos tokens, or an empty token body on a first-leg SPNEGO handshake being treated as a ticket.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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