spring-projects/spring-security · error · BadCredentialsException
<ticket validation failure message>
Error message
<ticket validation failure message>
What it means
CasAuthenticationProvider.validateAuthentication (via authenticateNow) catches a TicketValidationException from the CAS service ticket validator and rethrows it as a BadCredentialsException whose message is the original exception's message. The thrown message is whatever the ticket validator reported, e.g. 'Ticket ST-... not recognized'. Spring Security does this so CAS validation failures map onto the standard bad-credentials authentication failure flow (including event publishing and failure handling).
Source
Thrown at cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java:156
return result;
}
private CasAuthenticationToken authenticateNow(final Authentication authentication) throws AuthenticationException {
try {
Object credentials = authentication.getCredentials();
if (credentials == null) {
throw new BadCredentialsException("Authentication.getCredentials() cannot be null");
}
Assertion assertion = this.ticketValidator.validate(credentials.toString(), getServiceUrl(authentication));
UserDetails userDetails = loadUserByAssertion(assertion);
this.userDetailsChecker.check(userDetails);
Collection<GrantedAuthority> authorities = new ArrayList<>(
this.authoritiesMapper.mapAuthorities(userDetails.getAuthorities()));
authorities.add(FactorGrantedAuthority.fromAuthority(AUTHORITY));
return new CasAuthenticationToken(this.key, userDetails, credentials, authorities, userDetails, assertion);
}
catch (TicketValidationException ex) {
throw new BadCredentialsException(ex.getMessage(), ex);
}
}
/**
* Gets the serviceUrl. If the {@link Authentication#getDetails()} is an instance of
* {@link ServiceAuthenticationDetails}, then
* {@link ServiceAuthenticationDetails#getServiceUrl()} is used. Otherwise, the
* {@link ServiceProperties#getService()} is used.
* @param authentication
* @return
*/
private @Nullable String getServiceUrl(Authentication authentication) {
String serviceUrl;
if (authentication.getDetails() instanceof ServiceAuthenticationDetails) {
return ((ServiceAuthenticationDetails) authentication.getDetails()).getServiceUrl();
}
Assert.state(this.serviceProperties != null,
"serviceProperties cannot be null unless Authentication.getDetails() implements ServiceAuthenticationDetails.");View on GitHub (pinned to 96852e8860)
Solutions
- Read the wrapped cause (ex.getCause(), the original TicketValidationException) to see the exact validation failure reported by the CAS server.
- Verify the service URL used to request the ticket exactly matches the service parameter sent to /serviceValidate (use ServiceProperties and ServiceAuthenticationDetails consistently).
- Check clock synchronization (NTP) between the application and the CAS server, since expired/not-yet-valid tickets commonly stem from skew.
- Ensure the ticket is only validated once and not replayed on redirects or AJAX retries.
- Confirm network connectivity and TLS trust between the app and the CAS server's validation endpoint.
Example fix
// before: opaque message only
try { auth = provider.authenticate(token); } catch (BadCredentialsException e) { log.error(e.getMessage()); }
// after: surface the CAS-side cause
try { auth = provider.authenticate(token); }
catch (BadCredentialsException e) {
Throwable cause = e.getCause(); // TicketValidationException with CAS server detail
log.error("CAS ticket validation failed: {}", cause != null ? cause.getMessage() : e.getMessage());
} Defensive patterns
Strategy: try-catch
Validate before calling
// verify service URL matches ServiceProperties before authenticating assert serviceProperties.getService().equals(request.getRequestURL().toString());
Try / catch
try {
auth = provider.authenticate(token);
} catch (BadCredentialsException e) {
Throwable cause = e.getCause();
log.warn("CAS ticket rejected: {}", cause != null ? cause.getMessage() : e.getMessage());
// return 401 and redirect to CAS for a fresh ticket
} Prevention
- Keep ServiceProperties and the CAS client service URL identical
- Synchronize clocks via NTP with the CAS server
- Never replay a ticket; redirect anonymous users to CAS instead
- Monitor CAS server availability and TLS certificate trust
When it happens
Trigger: A user authenticates via CAS and CasAuthenticationProvider.authenticate -> authenticateNow calls the TicketValidator; the validator rejects the presented service ticket (expired, already used, service URL mismatch, or CAS server unreachable/rejecting).
Common situations: Ticket replay after browser refresh, clock skew between app server and CAS server, service/destination URL mismatch between the service ticket request and the validation request, misconfigured ticketValidator or serviceProperties, expired tickets, CAS server outage.
Related errors
- CasAuthenticationProvider.incorrectKey
- RunAsImplAuthenticationProvider.incorrectKey
- CasAuthenticationProvider.noServiceTicket
- Authentication.getCredentials() cannot be null
- Cannot pass null or empty values to constructor
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/684f7d26059a979a.
Report an issue: GitHub.