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
- Check the authorization rules for the destination — ensure a matcher grants the user's role/authority for the message pattern.
- Ensure the STOMP CONNECT carries authentication (e.g. CSRF token and session cookie, or a token header) so the user is authenticated before sends.
- Inspect published authorization events/logs ('Failed to authorize message...') to see which matcher denied the send.
- 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
- Keep destination authorization rules in sync with client destination patterns.
- Ensure STOMP CONNECT authenticates the user before message sends.
- Enable authorization event logging to audit denials.
- Test destination rules with each role in integration tests.
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.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/029598029a93b81d.
Report an issue: GitHub.