spring-projects/spring-security · warning · SessionAuthenticationException

ConcurrentSessionControlAuthenticationStrategy.exceededAllowed

ConcurrentSessionControlAuthenticationStrategy.exceededAllowed

Error message

Maximum sessions of {0} for this principal exceeded

What it means

ConcurrentSessionControlAuthenticationStrategy enforces a maximum number of simultaneous sessions per principal. When the limit is exceeded and exceptionIfMaximumExceeded is true (or the session list is null), it throws SessionAuthenticationException with the allowed count. This is the 'reject login instead of evicting oldest session' mode.

Solutions

  1. Set exceptionIfMaximumExceeded(false) to evict the least-recently-used session instead of rejecting the login
  2. Raise maximumSessions to a realistic value for your users' device usage
  3. Configure a distributed SessionRegistry (e.g. Spring Session backed) so stale sessions are cleaned across nodes
  4. Educate users or provide a 'sign out other sessions' flow

Example fix

// before
http.sessionManagement(s -> s.maximumSessions(1)
        .maxSessionsPreventsLogin(true)); // rejects new logins
// after
http.sessionManagement(s -> s.maximumSessions(1)
        .maxSessionsPreventsLogin(false)); // evicts oldest session instead
Defensive patterns

Strategy: try-catch

Validate before calling

SessionInformation[] active = sessionRegistry.getAllSessions(principal, false);
if (active != null && active.length >= maximumSessions && preventLogin) {
    // warn user or force a logout of other sessions before authenticating
    return;
}

Try / catch

try {
    authenticationManager.authenticate(token);
    strategy.onAuthentication(authentication, request, response);
} catch (SessionAuthenticationException e) {
    request.setAttribute("maxSessionsExceeded", true);
    response.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
}

Prevention

When it happens

Trigger: onAuthentication -> allowableSessionsExceeded is reached when the user authenticates while already holding maximumSessions sessions and exceptionIfMaximumExceeded=true; also thrown if the registry returns a null session list.

Common situations: maximumSessions configured (e.g. 1) with exceptionIfMaximumExceeded(true) and users sharing credentials across devices; session registry out of sync after app restart leaving stale sessions; load-balanced deployment without sticky/replicated sessions.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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

Appendix: source

Thrown at web/src/main/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategy.java:151

	 * @return either -1 meaning unlimited, or a positive integer to limit (never zero)
	 */
	protected int getMaximumSessionsForThisUser(Authentication authentication) {
		return this.sessionLimit.apply(authentication);
	}

	/**
	 * Allows subclasses to customise behaviour when too many sessions are detected.
	 * @param sessions either <code>null</code> or all unexpired sessions associated with
	 * the principal
	 * @param allowableSessions the number of concurrent sessions the user is allowed to
	 * have
	 * @param registry an instance of the <code>SessionRegistry</code> for subclass use
	 *
	 */
	protected void allowableSessionsExceeded(List<SessionInformation> sessions, int allowableSessions,
			SessionRegistry registry) throws SessionAuthenticationException {
		if (this.exceptionIfMaximumExceeded || (sessions == null)) {
			throw new SessionAuthenticationException(
					this.messages.getMessage("ConcurrentSessionControlAuthenticationStrategy.exceededAllowed",
							new Object[] { allowableSessions }, "Maximum sessions of {0} for this principal exceeded"));
		}
		// Determine least recently used sessions, and mark them for invalidation
		sessions.sort(Comparator.comparing(SessionInformation::getLastRequest));
		int maximumSessionsExceededBy = sessions.size() - allowableSessions + 1;
		List<SessionInformation> sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy);
		for (SessionInformation session : sessionsToBeExpired) {
			session.expireNow();
		}
	}

	/**
	 * Sets the <tt>exceptionIfMaximumExceeded</tt> property, which determines whether the
	 * user should be prevented from opening more sessions than allowed. If set to
	 * <tt>true</tt>, a <tt>SessionAuthenticationException</tt> will be raised which means
	 * the user authenticating will be prevented from authenticating. if set to
	 * <tt>false</tt>, the user that has already authenticated will be forcibly logged

View on GitHub (pinned to 96852e8860)