spring-projects/spring-security · warning

Negotiate Header was invalid: %s

Error message

Negotiate Header was invalid: %s

What it means

SpnegoAuthenticationProcessingFilter.doFilterInternal logs this warning when the AuthenticationManager throws an AuthenticationException while authenticating the SpnegoAuthenticationToken built from the Authorization: Negotiate header. Since a bad client token normally means 'continue the handshake' rather than an exception, this signals a server-side problem: Kerberos validation of the ticket failed. The security context is cleared and the failure handler runs (defaulting to HTTP 500).

Source

Thrown at kerberos/kerberos-web/src/main/java/org/springframework/security/kerberos/web/authentication/SpnegoAuthenticationProcessingFilter.java:184

				|| header.startsWith("Kerberos "))) {
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Received Negotiate Header for request " + request.getRequestURL() + ": " + header);
			}
			byte[] base64Token = header.substring(header.indexOf(" ") + 1).getBytes("UTF-8");
			byte[] kerberosTicket = Base64.getDecoder().decode(base64Token);
			KerberosServiceRequestToken authenticationRequest = new KerberosServiceRequestToken(kerberosTicket);
			authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
			Authentication authentication;
			try {
				if (this.authenticationManager == null) {
					throw new IllegalStateException("authenticationManager must be set");
				}
				authentication = this.authenticationManager.authenticate(authenticationRequest);
			}
			catch (AuthenticationException ex) {
				// That shouldn't happen, as it is most likely a wrong
				// configuration on the server side
				this.logger.warn("Negotiate Header was invalid: " + header, ex);
				this.securityContextHolderStrategy.clearContext();
				if (this.failureHandler != null) {
					this.failureHandler.onAuthenticationFailure(request, response, ex);
				}
				else {
					response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
					response.flushBuffer();
				}
				return;
			}
			this.sessionStrategy.onAuthentication(authentication, request, response);

			SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
			context.setAuthentication(authentication);
			this.securityContextHolderStrategy.setContext(context);
			this.securityContextRepository.saveContext(context, request, response);
			if (this.successHandler != null) {
				this.successHandler.onAuthenticationSuccess(request, response, authentication);

View on GitHub (pinned to 96852e8860)

Solutions

  1. Verify the keytab matches the service principal and the SPN the client uses (HTTP/fqdn@REALM); re-export with ktpass/kadmin if needed and confirm with `klist -k keytab`.
  2. Check the full warning stack trace in the logs — the underlying GSS/KrbException (e.g. 'Clock skew too great', 'Key version number for principal in key table is incorrect') names the root cause.
  3. Ensure server clock sync with the KDC (NTP) within the 5-minute skew window, and confirm kvno consistency between KDC and keytab.
  4. Validate KDC reachability and krb5.conf (default_realm, realms, dns_lookup) on the app server; test with `kinit -k -t keytab principal`.

Example fix

// before (bean wiring SPN that doesn't match the keytab)
validator.setServicePrincipal("HTTP/wrong-host@EXAMPLE.COM");

// after
validator.setServicePrincipal("HTTP/app.example.com@EXAMPLE.COM"); // matches keytab & client SPN
validator.setKeyTabLocation(new FileSystemResource("/etc/security/app.keytab"));
Defensive patterns

Strategy: try-catch

Validate before calling

// before deploying: verify keytab/SPN/KDC from the server host
// klist -k /etc/security/app.keytab
// kinit -k -t /etc/security/app.keytab HTTP/app.example.com@EXAMPLE.COM

Try / catch

The filter handles the AuthenticationException internally (clears the context, invokes failureHandler, default 500). Provide a custom failureHandler: filter.setFailureHandler((req, res, ex) -> { log.warn("SPNEGO failed", ex); res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid Negotiate token"); }); and correlate with the logged stack trace for the root KrbException.

Prevention

When it happens

Trigger: A browser sends a Negotiate header with a service ticket, SunJaasKerberosTicketValidator fails to validate it (keytab mismatch with the service principal, wrong SPN registered by the client, clock skew > 5 min, ticket replay, KDC unreachable), and authenticationManager.authenticate() throws, hitting the catch block at SpnegoAuthenticationProcessingFilter.java:184.

Common situations: Keytab generated for a different SPN than the URL the client resolved (e.g. HTTP/host@REALM mismatch); duplicate SPN registrations (setspn -X); AD/FreeIPA KDC not reachable from the app server; host clocks out of sync; outdated kerb5.conf/JAAS config; replayed cached tickets from a load-balanced setup without proper SPN setup.

Related errors


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