spring-projects/spring-security · error · AccessDeniedException

Access Denied

Error message

Access Denied

What it means

AuthorizationChannelInterceptor.preSend() authorizes inbound STOMP message sends using its preSendAuthorizationManager. If the AuthorizationResult is null or not granted (default-deny), it throws AccessDeniedException('Access Denied'), rejecting the message before it reaches the message handler.

Source

Thrown at messaging/src/main/java/org/springframework/security/messaging/access/intercept/AuthorizationChannelInterceptor.java:75

	 * Creates a new instance.
	 * @param preSendAuthorizationManager the {@link AuthorizationManager} to use. Cannot
	 * be null.
	 *
	 */
	public AuthorizationChannelInterceptor(AuthorizationManager<Message<?>> preSendAuthorizationManager) {
		Assert.notNull(preSendAuthorizationManager, "preSendAuthorizationManager cannot be null");
		this.preSendAuthorizationManager = preSendAuthorizationManager;
	}

	@Override
	public Message<?> preSend(Message<?> message, MessageChannel channel) {
		this.logger.debug(LogMessage.of(() -> "Authorizing message send"));
		AuthorizationResult result = this.preSendAuthorizationManager.authorize(this.authentication, message);
		this.eventPublisher.publishAuthorizationEvent(this.authentication, message, result);
		if (result == null || !result.isGranted()) { // default deny
			this.logger.debug(LogMessage.of(() -> "Failed to authorize message with authorization manager "
					+ this.preSendAuthorizationManager + " and result " + result));
			throw new AccessDeniedException("Access Denied");
		}
		this.logger.debug(LogMessage.of(() -> "Authorized message send"));
		return message;
	}

	/**
	 * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use
	 * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}.
	 */
	public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
		this.authentication = getAuthentication(securityContextHolderStrategy);
	}

	/**
	 * Use this {@link AuthorizationEventPublisher} to publish the
	 * {@link AuthorizationManager} result.
	 * @param eventPublisher
	 */

View on GitHub (pinned to 96852e8860)

Solutions

  1. Check the authorization rules for the destination — ensure a matcher grants the user's role/authority for the message pattern.
  2. Ensure the STOMP CONNECT carries authentication (e.g. CSRF token and session cookie, or a token header) so the user is authenticated before sends.
  3. Inspect published authorization events/logs ('Failed to authorize message...') to see which matcher denied the send.
  4. If anonymous access is intended, configure the AuthorizationManager to grant on the target destination.

Example fix

// before: no rule for the destination
classifier.authorize(authentication, message) -> deny
// after: add an explicit rule
.authorizeMessages("/user/queue/**", hasRole("USER"))
.anyMessage().denyAll();
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check in a channel interceptor
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null || !auth.getAuthorities().stream()
        .anyMatch(a -> a.getAuthority().equals("ROLE_USER"))) {
    throw new MessagingException("Not authorized for this destination");
}

Type guard

boolean isAuthorized(Message<?> message, Authentication auth) {
    return auth != null && auth.isAuthenticated()
        && auth.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_USER"));
}

Try / catch

try {
    channel.send(message);
} catch (AccessDeniedException | MessageDeliveryException e) {
    // Spring wraps AccessDeniedException in MessageDeliveryException on send
    log.warn("Message denied: {}", e.getMessage());
}

Prevention

When it happens

Trigger: A WebSocket/STOMP client sends a message to a destination whose @PreAuthorize/@SendToUser/AuthorizationManager rules do not grant access to the current authentication, or result is null (no decision), producing a default deny in preSend().

Common situations: Missing or anonymous authentication on the WebSocket session (no user attached during CONNECT); security rules configured for HTTP endpoints but not for message destinations; misconfigured authorize rules or wrong destination pattern in antMatchers/MessageMatcherReactor AuthorizationManager.

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/029598029a93b81d. Report an issue: GitHub.