spring-projects/spring-security · error · BadCredentialsException

CasAuthenticationProvider.incorrectKey

CasAuthenticationProvider.incorrectKey

Error message

The presented CasAuthenticationToken does not contain the expected key

What it means

CasAuthenticationProvider authenticates an already-established CasAuthenticationToken by comparing its stored keyHash with hashCode() of the provider's configured key. A mismatch means the token was produced by a different CAS provider/key (or tampered with), so BadCredentialsException is thrown. The key is a shared secret distinguishing which provider created stateless/stateful CAS tokens.

Source

Thrown at cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java:110

	@Override
	public void afterPropertiesSet() {
		Assert.notNull(this.authenticationUserDetailsService, "An authenticationUserDetailsService must be set");
		Assert.notNull(this.ticketValidator, "A ticketValidator must be set");
		Assert.notNull(this.statelessTicketCache, "A statelessTicketCache must be set");
		Assert.hasText(this.key,
				"A Key is required so CasAuthenticationProvider can identify tokens it previously authenticated");
		Assert.notNull(this.messages, "A message source must be set");
	}

	@Override
	public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException {
		if (!supports(authentication.getClass())) {
			return null;
		}
		// If an existing CasAuthenticationToken, just check we created it
		if (authentication instanceof CasAuthenticationToken) {
			if (this.key.hashCode() != ((CasAuthenticationToken) authentication).getKeyHash()) {
				throw new BadCredentialsException(this.messages.getMessage("CasAuthenticationProvider.incorrectKey",
						"The presented CasAuthenticationToken does not contain the expected key"));
			}
			return authentication;
		}

		// Ensure credentials are presented
		if ((authentication.getCredentials() == null) || "".equals(authentication.getCredentials())) {
			throw new BadCredentialsException(this.messages.getMessage("CasAuthenticationProvider.noServiceTicket",
					"Failed to provide a CAS service ticket to validate"));
		}

		boolean stateless = (authentication instanceof CasServiceTicketAuthenticationToken token
				&& token.isStateless());
		CasAuthenticationToken result = null;

		if (stateless) {
			// Try to obtain from cache
			result = this.statelessTicketCache.getByTicketId(authentication.getCredentials().toString());

View on GitHub (pinned to 96852e8860)

Solutions

  1. Ensure the provider key is identical everywhere it is configured (same CasAuthenticationProvider instance/key used to mint the token).
  2. Align keys across all nodes/environments (externalize to a shared property, e.g. cas.key).
  3. Re-authenticate from the original service ticket instead of re-passing an old CasAuthenticationToken.
  4. Catch BadCredentialsException for CAS tokens and force a fresh CAS login redirect.

Example fix

// before
CasAuthenticationProvider p1 = new CasAuthenticationProvider(); p1.setKey("ONE");
CasAuthenticationProvider p2 = new CasAuthenticationProvider(); p2.setKey("TWO"); // token minted by p1 fails here
// after
String sharedKey = env.getProperty("cas.provider-key");
p1.setKey(sharedKey); p2.setKey(sharedKey);
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure token key hash matches provider key before authenticating
if (token instanceof CasAuthenticationToken cat
        && cat.getKeyHash() != providerKey.hashCode()) {
    // token came from a different provider — re-authenticate
}

Type guard

null

Try / catch

try {
    return casAuthenticationProvider.authenticate(authentication);
} catch (BadCredentialsException e) {
    // key mismatch: restart CAS login flow
    return redirectService.initiateCasLogin();
}

Prevention

When it happens

Trigger: Passing a CasAuthenticationToken to authenticate() when the provider's <key> (constructor arg or CasAuthenticationProvider.setKey) differs from the key used when the token was created — e.g. two provider instances, key changed between deployments, or a token from another security filter chain.

Common situations: Multiple CAS providers configured with different keys in the same app; changing the key in config (or moving to environment-specific keys) invalidating previously issued tokens/tickets; load-balanced nodes with inconsistent key configuration; replaying a serialized token across apps.

Understand the failure class

Related errors


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