Manage authentication context across message processing threads and inject authenticated principals into message handler methods.
Classic approach for security context propagation - obtains Authentication from message headers and populates SecurityContext during message processing.
/**
* ChannelInterceptor that obtains Authentication from message headers
* and populates SecurityContext during message processing.
*
* @since 4.0
*/
public final class SecurityContextChannelInterceptor
implements ExecutorChannelInterceptor, ChannelInterceptor {
/**
* Default header name for storing authentication in message headers.
*/
public static final String USER_HEADER = "simpUser";
/**
* Creates interceptor using default USER_HEADER for authentication.
*/
public SecurityContextChannelInterceptor();
/**
* Creates interceptor with custom header name for authentication.
*
* @param authenticationHeaderName name of header containing authentication
*/
public SecurityContextChannelInterceptor(String authenticationHeaderName);
/**
* Sets anonymous authentication to use when no authentication is present.
*
* @param authentication the anonymous authentication
*/
public void setAnonymousAuthentication(Authentication authentication);
/**
* Sets the SecurityContextHolderStrategy for managing security context.
*
* @param strategy the strategy to use
*/
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy strategy);
/**
* Intercepts message before sending and sets up security context.
*
* @param message the message being sent
* @param channel the channel
* @return the message
*/
public Message<?> preSend(Message<?> message, MessageChannel channel);
/**
* Cleans up security context after message send completes.
*
* @param message the message
* @param channel the channel
* @param sent whether send succeeded
* @param ex exception if send failed
*/
public void afterSendCompletion(
Message<?> message,
MessageChannel channel,
boolean sent,
Exception ex
);
/**
* Sets up security context before handling message.
*
* @param message the message
* @param channel the channel
* @param handler the message handler
* @return the message
*/
public Message<?> beforeHandle(
Message<?> message,
MessageChannel channel,
MessageHandler handler
);
/**
* Cleans up security context after message handling.
*
* @param message the message
* @param channel the channel
* @param handler the message handler
* @param ex exception if handling failed
*/
public void afterMessageHandled(
Message<?> message,
MessageChannel channel,
MessageHandler handler,
Exception ex
);
}Usage Example:
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.security.config.annotation.web.socket.AbstractSecurityWebSocketMessageBrokerConfigurer;
import org.springframework.security.messaging.context.SecurityContextChannelInterceptor;
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override
protected void configureInbound(ChannelRegistration registration) {
registration.interceptors(new SecurityContextChannelInterceptor());
}
}Modern approach (since 6.2) - captures Authentication from current SecurityContext in preSend and propagates via message headers. Cannot be used together with SecurityContextChannelInterceptor.
/**
* ExecutorChannelInterceptor that captures Authentication from current
* SecurityContext and propagates it via message headers.
*
* @since 6.2
*/
public final class SecurityContextPropagationChannelInterceptor
implements ExecutorChannelInterceptor {
/**
* Default header name for storing authentication in message headers.
*/
public static final String USER_HEADER = "simpUser";
/**
* Creates interceptor using default USER_HEADER for authentication.
*/
public SecurityContextPropagationChannelInterceptor();
/**
* Creates interceptor with custom header name for authentication.
*
* @param authenticationHeaderName name of header to store authentication
*/
public SecurityContextPropagationChannelInterceptor(String authenticationHeaderName);
/**
* Sets the SecurityContextHolderStrategy for managing security context.
*
* @param strategy the strategy to use
*/
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy strategy);
/**
* Sets anonymous authentication to use when no authentication is present.
*
* @param authentication the anonymous authentication
*/
public void setAnonymousAuthentication(Authentication authentication);
/**
* Captures current authentication and stores in message header.
*
* @param message the message
* @param channel the channel
* @return message with authentication in header
*/
public Message<?> preSend(Message<?> message, MessageChannel channel);
/**
* Restores security context from message header before handling.
*
* @param message the message
* @param channel the channel
* @param handler the message handler
* @return the message
*/
public Message<?> beforeHandle(
Message<?> message,
MessageChannel channel,
MessageHandler handler
);
/**
* Restores security context from message header after receiving.
*
* @param message the message
* @param channel the channel
* @return the message
*/
public Message<?> postReceive(Message<?> message, MessageChannel channel);
/**
* Cleans up security context after message handling.
*
* @param message the message
* @param channel the channel
* @param handler the message handler
* @param ex exception if handling failed
*/
public void afterMessageHandled(
Message<?> message,
MessageChannel channel,
MessageHandler handler,
Exception ex
);
}Usage Example:
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.security.config.annotation.web.socket.AbstractSecurityWebSocketMessageBrokerConfigurer;
import org.springframework.security.messaging.context.SecurityContextPropagationChannelInterceptor;
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override
protected void configureInbound(ChannelRegistration registration) {
// Modern approach - captures from current SecurityContext
registration.interceptors(new SecurityContextPropagationChannelInterceptor());
}
}Resolves method parameters annotated with @AuthenticationPrincipal to inject the authenticated user's principal into message handler methods.
/**
* HandlerMethodArgumentResolver that resolves parameters annotated
* with @AuthenticationPrincipal to inject the authenticated principal.
*
* @since 4.0
*/
public final class AuthenticationPrincipalArgumentResolver
implements HandlerMethodArgumentResolver {
/**
* Checks if this resolver supports the given parameter.
*
* @param parameter the method parameter
* @return true if parameter is annotated with @AuthenticationPrincipal
*/
public boolean supportsParameter(MethodParameter parameter);
/**
* Resolves the authenticated principal from SecurityContext.
*
* @param parameter the method parameter
* @param message the current message
* @return the authenticated principal
*/
public Object resolveArgument(MethodParameter parameter, Message<?> message);
/**
* Sets the SecurityContextHolderStrategy for obtaining authentication.
*
* @param securityContextHolderStrategy the strategy to use
* @since 5.8
*/
public void setSecurityContextHolderStrategy(
SecurityContextHolderStrategy securityContextHolderStrategy
);
/**
* Sets template defaults for annotation expression evaluation.
*
* @param templateDefaults the template defaults
* @since 6.4
*/
public void setTemplateDefaults(
AnnotationTemplateExpressionDefaults templateDefaults
);
}Usage Example:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.messaging.context.AuthenticationPrincipalArgumentResolver;
import org.springframework.stereotype.Controller;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import java.util.List;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthenticationPrincipalArgumentResolver());
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").withSockJS();
}
}
@Controller
public class ChatController {
@MessageMapping("/chat")
public void handleChatMessage(
@AuthenticationPrincipal UserDetails user,
String message
) {
String username = user.getUsername();
// Process message from authenticated user...
}
@MessageMapping("/profile")
public void handleProfileUpdate(
@AuthenticationPrincipal(expression = "username") String username,
ProfileUpdate update
) {
// username is extracted directly from principal
// Process profile update...
}
}import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.security.config.annotation.web.socket.AbstractSecurityWebSocketMessageBrokerConfigurer;
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override
protected void configureInbound(ChannelRegistration registration) {
// Choose ONE of these approaches:
// Option 1: Classic approach
registration.interceptors(new SecurityContextChannelInterceptor());
// Option 2: Modern approach (recommended for new applications)
// registration.interceptors(new SecurityContextPropagationChannelInterceptor());
}
}@Override
protected void configureInbound(ChannelRegistration registration) {
// Use custom header name instead of default "simpUser"
SecurityContextChannelInterceptor interceptor =
new SecurityContextChannelInterceptor("customAuthHeader");
registration.interceptors(interceptor);
}import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
@Override
protected void configureInbound(ChannelRegistration registration) {
SecurityContextChannelInterceptor interceptor =
new SecurityContextChannelInterceptor();
// Set anonymous authentication for unauthenticated users
AnonymousAuthenticationToken anonymousAuth = new AnonymousAuthenticationToken(
"key",
"anonymousUser",
AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")
);
interceptor.setAnonymousAuthentication(anonymousAuth);
registration.interceptors(interceptor);
}import org.springframework.security.core.context.SecurityContextHolderStrategy;
@Override
protected void configureInbound(ChannelRegistration registration) {
SecurityContextChannelInterceptor interceptor =
new SecurityContextChannelInterceptor();
// Use custom strategy (e.g., for testing or custom security context management)
SecurityContextHolderStrategy customStrategy = // ... custom implementation
interceptor.setSecurityContextHolderStrategy(customStrategy);
registration.interceptors(interceptor);
}import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
@Controller
public class MessageHandlers {
// Inject entire UserDetails object
@MessageMapping("/simple")
public void handleSimple(@AuthenticationPrincipal UserDetails user) {
String username = user.getUsername();
// ...
}
// Extract specific property using SpEL expression
@MessageMapping("/username")
public void handleUsername(
@AuthenticationPrincipal(expression = "username") String username
) {
// username extracted directly
// ...
}
// Work with custom principal types
@MessageMapping("/custom")
public void handleCustom(@AuthenticationPrincipal CustomUserPrincipal principal) {
String email = principal.getEmail();
// ...
}
// Can also inject full Authentication if needed
@MessageMapping("/auth")
public void handleAuth(Authentication authentication) {
Object principal = authentication.getPrincipal();
// ...
}
}import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.security.config.annotation.web.socket.AbstractSecurityWebSocketMessageBrokerConfigurer;
import org.springframework.security.messaging.context.AuthenticationPrincipalArgumentResolver;
import org.springframework.security.messaging.context.SecurityContextChannelInterceptor;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import java.util.List;
@Configuration
public class CompleteWebSocketSecurityConfig
extends AbstractSecurityWebSocketMessageBrokerConfigurer {
@Override
protected void configureInbound(ChannelRegistration registration) {
// Add security context propagation
registration.interceptors(new SecurityContextChannelInterceptor());
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
// Enable @AuthenticationPrincipal in message handlers
argumentResolvers.add(new AuthenticationPrincipalArgumentResolver());
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOrigins("*")
.withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic", "/queue");
registry.setApplicationDestinationPrefixes("/app");
registry.setUserDestinationPrefix("/user");
}
}Important: Choose ONE interceptor - do not use both in the same configuration.
Step 1: Replace interceptor registration:
// Old (Classic)
@Override
protected void configureInbound(ChannelRegistration registration) {
registration.interceptors(new SecurityContextChannelInterceptor());
}
// New (Modern)
@Override
protected void configureInbound(ChannelRegistration registration) {
registration.interceptors(new SecurityContextPropagationChannelInterceptor());
}Step 2: Verify authentication is available in SecurityContext before message processing
Step 3: Test thoroughly - behavior may differ slightly
Always check for null authentication:
@MessageMapping("/secure")
public void handleSecure(
@AuthenticationPrincipal(required = false) UserDetails user,
String message
) {
if (user == null) {
// Handle unauthenticated case
logger.warn("Unauthenticated message received");
return;
}
// Process authenticated message
}If security context is not propagated, @AuthenticationPrincipal will fail:
// Ensure interceptor is registered
@Override
protected void configureInbound(ChannelRegistration registration) {
// Must have one of these:
registration.interceptors(new SecurityContextChannelInterceptor());
// OR
registration.interceptors(new SecurityContextPropagationChannelInterceptor());
}Causes:
Solutions:
// Ensure interceptor is registered
registration.interceptors(new SecurityContextPropagationChannelInterceptor());
// Check if authentication exists
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null) {
logger.warn("No authentication in security context");
}
// Configure anonymous authentication if needed
interceptor.setAnonymousAuthentication(anonymousAuth);Causes:
Solutions:
// Use InheritableThreadLocalSecurityContextHolderStrategy for thread pools
SecurityContextHolderStrategy strategy =
new InheritableThreadLocalSecurityContextHolderStrategy();
interceptor.setSecurityContextHolderStrategy(strategy);Symptom: Security context not working correctly
Cause: Both interceptors registered
Solution: Remove one interceptor - use only SecurityContextChannelInterceptor OR SecurityContextPropagationChannelInterceptor, not both.