spring-projects/spring-security · warning

Your keytab is in the classpath. This file needs special…

Error message

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.

What it means

SunJaasKerberosTicketValidator.afterPropertiesSet logs this warning when keyTabLocation is a ClassPathResource. The keytab contains the service's long-term Kerberos keys; shipping it on the classpath is insecure (anyone with the artifact can extract it) and Krb5LoginModule may fail to load it from inside a JAR. Validation of service tickets then proceeds with the keytab path after stripping any file: prefix.

Solutions

  1. Place the keytab at a protected filesystem path and point keyTabLocation at a FileSystemResource or file:/etc/... URL.
  2. Set key-tab-location: file:/etc/security/app.keytab (absolute path) in application.yml instead of classpath:.
  3. Mount the keytab via a secret/configMap volume in containerized deployments and chmod 600 it.
  4. Rotate the key (kadmin: ktadd -k new.keytab) if it was ever packaged into an artifact or committed to git.

Example fix

// before
validator.setKeyTabLocation(new ClassPathResource("kerberos/app.keytab"));

// after
validator.setKeyTabLocation(new FileSystemResource("/etc/security/app.keytab"));
// or key-tab-location: file:/etc/security/app.keytab
Defensive patterns

Strategy: validation

Validate before calling

Resource keytab = validator.getKeyTabLocation();
if (keytab instanceof ClassPathResource) {
    throw new IllegalStateException("Kerberos keytab must not live on the classpath; use file:/etc/security/app.keytab");
}

Type guard

boolean isExternalKeytab(Resource r) { return r != null && !(r instanceof ClassPathResource); }

Try / catch

Not an exception — only LOG.warn during bean initialization; enforce the check in your own @PostConstruct/bean customization instead of relying on try-catch.

Prevention

When it happens

Trigger: Configuring the SunJaasKerberosTicketValidator bean (setKeyTabLocation(new ClassPathResource("...")) or spring.security.kerberos.key-tab-location=classpath:...) and letting Spring call afterPropertiesSet during context startup.

Common situations: application.yml with key-tab-location: classpath:app.keytab; keytab copied into src/main/resources; tutorials/docker images that bake the keytab into the application JAR; team members reusing a committed sample configuration.

Related errors


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

Appendix: source

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

			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
		// Java 7
		if (keyTabLocationAsString.startsWith("file:")) {
			keyTabLocationAsString = keyTabLocationAsString.substring(5);
		}
		LoginConfig loginConfig = new LoginConfig(keyTabLocationAsString, this.servicePrincipal, this.realmName,
				this.multiTier, this.debug, this.refreshKrb5Config);
		Set<Principal> princ = new HashSet<Principal>(1);
		princ.add(new KerberosPrincipal(this.servicePrincipal));
		Subject sub = new Subject(false, princ, new HashSet<Object>(), new HashSet<Object>());
		LoginContext lc = new LoginContext("", sub, null, loginConfig);
		lc.login();
		this.serviceSubject = lc.getSubject();

View on GitHub (pinned to 96852e8860)