I can create Exchanges,Queues/bindings with #Configuration & #Bean cleanly (as below) but I did not find a way to create VirtualHost in similar way. I just want to use default 'guest' user for this virtual host. Is there a way? I have 1 producer and 3 listeners running in separate apps. I thought it would be easy to have a class with this configuration and copy it to all these apps. I think this is a very common requirement. What is the best way to create required config in this scenario?
#Configuration
public class amqpConfiguration {
#Autowired
RabbitTemplate rabbitTemplate;
#Bean
TopicExchange testExchange() {
return new TopicExchange("test.exchange");
}
TopicExchange errorExchange() {
return new TopicExchange("error.change");
}
#Bean
Queue erorQueue() {
return new Queue("error.q", true);
}
#Bean
Binding errorQueueBinding(Queue erorQueue, TopicExchange errorExchange) {
return BindingBuilder.bind(erorQueue).to(errorExchange).with("error.q");
}
#Bean
Queue testQueue() {
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-dead-letter-exchange", "test.exchange");
args.put("x-dead-letter-routing-key", "error.q");
return new Queue("test.q", true, false, false, args);
}
#Bean
Binding inQueueBinding(Queue testQueue, TopicExchange testExchange) {
return BindingBuilder.bind(testQueue).to(testExchange).with("test.q");
}
}
No, you can do that only from the Broker perspective, or using Management Plugin HTTP API:
/api/vhosts/name - An individual virtual host. As a virtual host usually only has a name, you do not need an HTTP body when PUTing one of these.
Well, from big height yes, you can do that: using Spring RestTemplate to perform that REST API to create a desired vhost.
Related
CustomPermissionEvaluator with database.
On a REST Controller or Service I would like to set a custim Spring Security #preauthorize in order to check access on method according user rights.
User right is my application to access to a resource (page, service etc...) are specific and handled by an habilitation service call database....
They are not loaded in userDetails.
So, how could I do to configure CustomPermissionEvaluator and do I need to load permissions on login or can call my services in the evaluator.
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class ContextSecurityConfiguration {
#Bean
public PermissionEvaluator functionPermissionEvaluator() {
return new XxxPermissionEvaluator ();
}
#Bean
protected DefaultMethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(this.functionPermissionEvaluator());
return expressionHandler;
}
}
Evaluator
#Component
public class XxxPermissionEvaluator implements PermissionEvaluator {
#Autowired
private ApplicationContext applicationContext;
#Override
public boolean hasPermission(Authentication authentication, Object entity, Object permission) {
Optional<String> optionalUserId = SecurityUtils.getCurrentUserLogin(authentication);
return optionalUserId.map(userId -> {
EntityValidatorFactory entityValidatorFactory = applicationContext.getBean(EntityValidatorFactory.class);
EntityValidator entityValidator = entityValidatorFactory.get(entity);
return entityValidator.isUserAllowedToEntity(userId, entity, (String) permission);
}).orElse(false);
}
#Override
public boolean hasPermission(Authentication authentication, Serializable serializable, String s, Object o) {
throw new PrimaClaimsRuntimeException("hasPermission is not implemented.");
}
}
My pb is my custom evaluator call a database service it has to inject it.
Or it's a bit weird in a configuration class to injects service etc... scanned else where by the same configuration
So calling a service is it a good way you should I load permission on Login in userDetail...?
In XML configuration it's possible to refer to a property file defining user credentials. For example:
<security:user-service id="userDetailsService" properties="classpath:users.properties"/>
However in java configuration there is no default alternative available (that I'm aware of) but writing your own implementation like:
#Bean
public UserDetailsService userDetailsService() {
Properties properties = new Properties();
try {
File file = ResourceUtils.getFile("classpath:users.properties");
properties.load(new FileInputStream(file));
return new InMemoryUserDetailsManager(properties);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
I was wondering if it would make a nice addition to have this provided by the spring security framework out of the box. Something like :
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication("classpath:user.properties")
...
or
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUserCredentialsFile("classpath:user.properties")
...
I'm not sure if adding this improvement to Spring Security makes sense. As you had outlined, the equivalent Java Configuration would be:
#Bean
public UserDetailsService userDetailsService() throws Exception {
Properties users = PropertiesLoaderUtils.loadAllProperties("users.properties");
return new InMemoryUserDetailsManager(users);
}
This looks pretty straight forward for the user. What do you think?
I use Spring cloud Spring service connector to connect Rabbitmq service on CloudFoundry.
public class CloudConfig extends AbstractCloudConfig {
#Bean
public ConnectionFactory rabbitFactory()
{
return connectionFactory().rabbitConnectionFactory();
}
}
But I need to declare a CachingConnectionFactory and set its PublisherConfirms true. Because we need use publisherConfirm to check ack when we send message to queue. I have no idea about how to inject the connectionFactory which is got from cloud spring service connector. Or how we could handle this situation.
The documentation includes examples of customizing details of the connection provided by Connectors.
In your case, you should be able to do something like this:
#Bean
public RabbitConnectionFactory rabbitFactory() {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("publisherConfirms", true);
RabbitConnectionFactoryConfig rabbitConfig = new RabbitConnectionFactoryConfig(properties);
return connectionFactory().rabbitConnectionFactory(rabbitConfig);
}
You can reconfigure the CCF created by the connector as follows:
#Bean
public SmartInitializingSingleton factoryConfigurer() {
return new SmartInitializingSingleton() {
#Autowired
private CachingConnectionFactory connectionFactory;
#Override
public void afterSingletonsInstantiated() {
this.connectionFactory.setPublisherConfirms(true);
}
};
}
You must be sure not to perform any RabbitMQ operations before the application context is fully initialized (which is best practice anyway).
This is RabbitTemplate
#Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMandatory(true);
template.setMessageConverter(new Jackson2JsonMessageConverter());
template.setConfirmCallback((correlationData, ack, cause) -> {
if (!ack) {
System.out.println("send message failed: " + cause + correlationData.toString());
} else {
System.out.println("Publisher Confirm" + correlationData.toString());
}
});
return template;
}
This is spring-cloud config:
#Bean
public ConnectionFactory rabbitConnectionFactory() {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("publisherConfirms", true);
RabbitConnectionFactoryConfig rabbitConfig = new RabbitConnectionFactoryConfig(properties);
return connectionFactory().rabbitConnectionFactory(rabbitConfig);
}
When I use this sender to send message.The result is not expected.
#Component
public class TestSender {
#Autowired
private RabbitTemplate rabbitTemplate;
#Scheduled(cron = "0/5 * * * * ? ")
public void send() {
System.out.println("===============================================================");
this.rabbitTemplate.convertAndSend(EXCHANGE, "routingkey", "hello world",
(Message m) -> {
m.getMessageProperties().setHeader("tenant", "aaaaa");
return m;
}, new CorrelationData(UUID.randomUUID().toString()));
Date date = new Date();
System.out.println("Sender Msg Successfully - " + date);
}
}
Hi I am developing Spring-boot-RabbitMQ version 1.6.I am having few queries while developing the application. Read the docs and browsed other stack overflow question but i cannot get few things clear(Might be because of my bad memory).
It would be great if some one answers my questions.
1) Currently i am having 4-Producers and 4-Consumers.Producer may produce millions of messages or events so using a single connection for both producer & consumer will block consumer to consume the messages.So what i would thought is creating separate connections for producer and consumer so that both will not block and will give some performance improvement.Am i correct with this approach?
2) I am using CachingConnectionFactory in order to create connection using SimpleRabbitListenerContainerFactory.While making call to this factory whether it will return new connection for us?So if we use CachingConnectionFactory do we really need to write a separate connection factories for both Producer & consumer.Please find my below
1)Configuration class
#Configuration
#EnableRabbit
public class RabbitMqConfiguration{
#Autowired
private CachingConnectionFactory cachingConnectionFactory;
#Value("${concurrent.consumers}")
public int concurrent_consumers;
#Value("${max.concurrent.consumers}")
public int max_concurrent_consumers;
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(cachingConnectionFactory);
factory.setConcurrentConsumers(concurrent_consumers);
factory.setMaxConcurrentConsumers(max_concurrent_consumers);
factory.setMessageConverter(jsonMessageConverter());
return factory;
}
#Bean
public MessageConverter jsonMessageConverter()
{
final Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
return converter;
}
}
2)Producer Class
#Configuration
public class TaskProducerConfiguration extends RabbitMqConfiguration {
#Value("${queue1}")
public String queue1;
#Value("${queue2}")
public String queue2;
#Value("${queue3}")
public String queue1;
#Value("${queue4}")
public String queue2;
#Value("${spring.rabbit.exchange}")
public String exchange;
#Autowired
private CachingConnectionFactory cachingConnectionFactory;
#Primary
#Bean
public RabbitTemplate getQueue1Template()
{
RabbitTemplate template = new RabbitTemplate(cachingConnectionFactory);
template.setRoutingKey(this.queue1);
template.setMessageConverter(jsonMessageConverter());
return template;
}
#Bean
public RabbitTemplate getQueue2Template()
{
RabbitTemplate template = new RabbitTemplate(cachingConnectionFactory);
template.setRoutingKey(this.queue2);
template.setMessageConverter(jsonMessageConverter());
return template;
}
#Bean
public RabbitTemplate getQueue3Template()
{
RabbitTemplate template = new RabbitTemplate(cachingConnectionFactory);
template.setRoutingKey(this.queue3);
template.setMessageConverter(jsonMessageConverter());
return template;
}
#Bean
public RabbitTemplate getQueue4Template()
{
RabbitTemplate template = new RabbitTemplate(cachingConnectionFactory);
template.setRoutingKey(this.queue4);
template.setMessageConverter(jsonMessageConverter());
return template;
}
#Bean(name="queue1Bean")
public Queue queue1()
{
return new Queue(this.queue1);
}
#Bean(name="queue2Bean")
public Queue queue2()
{
return new Queue(this.queue2);
}
#Bean(name="queue3Bean")
public Queue queue3()
{
return new Queue(this.queue3);
}
#Bean(name="queue4Bean")
public Queue queue4()
{
return new Queue(this.queue4);
}
#Bean
TopicExchange exchange() {
return new TopicExchange(exchange);
}
#Bean
List<Binding> bindings(Queue queue1Bean,Queue queue2Bean,Queue queue3Bean,Queue queue4Bean, TopicExchange exchange) {
List<Binding> bindingList = new ArrayList<Binding>();
bindingList.add(BindingBuilder.bind(queue1Bean).to(exchange).with(this.queue1));
bindingList.add(BindingBuilder.bind(queue2Bean).to(exchange).with(this.queue2));
bindingList.add(BindingBuilder.bind(queue3Bean).to(exchange).with(this.queue3));
bindingList.add(BindingBuilder.bind(queue4Bean).to(exchange).with(this.queue4));
return bindingList;
}
}
3) Receiver Class(Just Shared one receiver class rest of the 3-receiver classes are one and the same except queue name & routing key).
#Component
public class Queue1Receiver {
#Autowired
private TaskProducer taskProducer;
#Value("${queue1}")
public String queue1;
#RabbitListener(id="queue1",containerFactory="rabbitListenerContainerFactory",queues = "#{queue1Bean}")
public void handleQueue1Message(TaskMessage taskMessage,#Header(AmqpHeaders.CONSUMER_QUEUE) String queue)
{
System.out.println("Queue::"+queue);
System.out.println("CustomerId: " + taskMessage.getCustomerID());
if(taskMessage.isHasQueue2()){
taskProducer.sendQueue2Message(taskMessage);
}
if(taskMessage.isHasQueue3()){
taskProducer.sendQueue3Message(taskMessage);
}
if(taskMessage.isHasQueue4()){
taskProducer.sendQueue4Message(taskMessage);
}
}
#Bean
public Queue queue1Bean() {
// This queue has the following properties:
// name: my_durable,durable: true,exclusive: false,auto_delete: false
return new Queue(queue1, true, false, false);
}
}
Your help should be appreciable.
Note : Down Voters please register your comment before down voting so that in future i can avoid the mistake.
Edited based on comments by Gary Russell:
1)RabbitMqConfiguration
#Configuration
#EnableRabbit
public class RabbitMqConfiguration{
#Value("${concurrent.consumers}")
public int concurrent_consumers;
#Value("${max.concurrent.consumers}")
public int max_concurrent_consumers;
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setConcurrentConsumers(concurrent_consumers);
factory.setMaxConcurrentConsumers(max_concurrent_consumers);
factory.setMessageConverter(jsonMessageConverter());
return factory;
}
#Bean
public CachingConnectionFactory connectionFactory()
{
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setCacheMode(CacheMode.CONNECTION);
return connectionFactory;
}
#Bean
public MessageConverter jsonMessageConverter()
{
final Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
return converter;
}
}
using a single connection for both producer & consumer will block consumer to consume the messages`
What leads you to believe that? A single connection will generally be fine. If you really want separate connections, change the connection factory cacheMode to CONNECTION.
You can use connection pooling in the same case keeping the pool size appropriate may solve the problem.As suggested in the above answer both producer and consumer are using the same connection so pooling might help you out instead.
I am trying to set up spring with websockets and STOMP.
On the client, I send a header variable
'simpSessionId':%session_id%
However, on receiving the message, spring it always places the supplied header in a key called nativeHeaders and a default simpSessionId in the header root.
{simpMessageType=MESSAGE, stompCommand=SEND, nativeHeaders={SPRING.SESSION.ID=[5b1f11d0-ad92-4855-ae44-b2052ecd76d8], Content-Type=[application/json], X-Requested-With=[XMLHttpRequest], simpSessionId=[5b1f11d0-ad92-4855-ae44-b2052ecd76d8], accept-version=[1.2,1.1,1.0], heart-beat=[0,0], destination=[/mobile-server/ping], content-length=[15]}, simpSessionAttributes={}, simpSessionId=1, simpDestination=/mobile-server/ping}
Any ideas how to have spring pick up the supplied session id instead?
Edited
Ok, I have a mobile phone app and a website hitting the same server. I need to be able to set up a webocket on the mobile phone app.
On the mobile phone app, I login to the server through a traditional REST endpoint, and I receive a session-id in the response if successful.
I use webstomp-client on the mobile phone, Spring 4.1.9, Spring Security 4.1, Spring Session 1.2.0.
I would ideally login to the STOMP websocket on the socket CONNECT using a token, but I understand that his is currently impossible because webstomp-client doesn't pass custom headers on CONNECT.
I have two problems:
How do I pass the session id that I retrieve on the REST Login in subsequent requests? I've tried adding headers such as SPRING.SESSION.ID, but stepping through the code I always see the message processing going back to the simpSessionId which is always defaulted to 1, 2 etc. I've tried extending the AbstractSessionWebsocketMessageBrokerConfigurer, but it doesn't pick up my session id, it always looks in the simpSessionAttributes, which is always empty.
The code also seems to try to get the http session, which is a web browser scenario. I'm assuming I should just ignore this
Sessions expire. What should be the strategy for a session that may have expired? Shouldn't I pass a remember-me style authentication token as well? Or should I rely on some everlasting stateless session? This is not clear to me and this aspect seems to be undocumented.
Obviously, I'm doing something very wrong. Here's my config:
#Configuration
#EnableRedisHttpSession(maxInactiveIntervalInSeconds=1200)
public class SessionConfig {
#Inject
ContentNegotiationManager contentNegotiationManager;
#Bean
public RedisConnectionFactory redisConnectionFactory(
#Value("${spring.redis.host}") String host,
#Value("${spring.redis.password}") String password,
#Value("${spring.redis.port}") Integer port) {
JedisConnectionFactory redis = new JedisConnectionFactory();
redis.setUsePool(true);
redis.setHostName(host);
redis.setPort(port);
redis.setPassword(password);
redis.afterPropertiesSet();
return redis;
}
#Bean
public RedisTemplate<String,ExpiringSession> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, ExpiringSession> template = new RedisTemplate<String, ExpiringSession>();
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(connectionFactory);
return template;
}
#Bean
public <S extends ExpiringSession>SessionRepositoryFilter<? extends ExpiringSession> sessionRepositoryFilter(SessionRepository<S> sessionRepository) {
return new SessionRepositoryFilter<S>(sessionRepository);
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
#Bean
public HttpSessionStrategy httpSessionStrategy(){
return new SmartSessionStrategy();
}
#Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("JSESSIONID");
serializer.setCookiePath("/");
serializer.setUseSecureCookie(true);
serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
return serializer;
}
}
===
public class SessionWebApplicationInitializer extends AbstractHttpSessionApplicationInitializer {
public SessionWebApplicationInitializer() {
}
public SessionWebApplicationInitializer(Class<?>... configurationClasses) {
super(configurationClasses);
}
#Override
protected void beforeSessionRepositoryFilter(ServletContext servletContext) {
Dynamic registration = servletContext.addFilter("openSessionInViewFilter", new OpenSessionInViewFilter());
if (registration == null) {
throw new IllegalStateException(
"Duplicate Filter registration for openSessionInViewFilter. Check to ensure the Filter is only configured once.");
}
registration.setAsyncSupported(false);
EnumSet<DispatcherType> dispatcherTypes = getSessionDispatcherTypes();
registration.addMappingForUrlPatterns(dispatcherTypes, false,"/*");
}
}
==
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig<S extends ExpiringSession> extends AbstractSessionWebsocketMessageBrokerConfigurer<S>{
#Inject
SessionRepository<S> sessionRepository;
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/mobile-server");
config.setUserDestinationPrefix("/mobile-user");
}
#Override
public void configureStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/ws")
.setHandshakeHandler(new SessionHandShakeHandler(new TomcatRequestUpgradeStrategy()))
.setAllowedOrigins("*")
.withSockJS()
.setSessionCookieNeeded(false)
;
}
#Override
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
registration.setMessageSizeLimit(512 * 1024);
registration.setSendBufferSizeLimit(1024 * 1024);
registration.setSendTimeLimit(40000);
}
#Bean
public WebSocketConnectHandler<S> webSocketConnectHandler(SimpMessageSendingOperations messagingTemplate, UsorManager userMgr) {
return new WebSocketConnectHandler<S>(messagingTemplate, userMgr);
}
#Bean
public WebSocketDisconnectHandler<S> webSocketDisconnectHandler(SimpMessageSendingOperations messagingTemplate, WebSocketManager repository) {
return new WebSocketDisconnectHandler<S>(messagingTemplate, repository);
}
}
====
#Configuration
public class WebSocketSecurity extends AbstractSecurityWebSocketMessageBrokerConfigurer{
ApplicationContext context = null;
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}
#Override
protected boolean sameOriginDisabled() {
return true;
}
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.nullDestMatcher().permitAll()
.simpSubscribeDestMatchers("/user/queue/errors").permitAll()
.simpDestMatchers("/mobile-server/ping").authenticated()
.simpDestMatchers("/mobile-server/csrf").authenticated()
.simpDestMatchers("/mobile-server/**").hasRole("ENDUSER")
.simpSubscribeDestMatchers("/user/**", "/topic/**").hasRole("ENDUSER")
.anyMessage().denyAll();
}
}
===
I have removed some additional security configurations I have here for brevity sake.
#Configuration
#EnableWebSecurity
#Order(100)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final String REMEMBER_ME_COOKIE = "SPRING_SECURITY_REMEMBER_ME_COOKIE";
#Inject
FilterInvocationSecurityMetadataSource securityMetadataSource;
#Inject
SessionRepositoryFilter<? extends ExpiringSession> sessionRepositoryFilter;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setSaltSource(saltSource);
provider.setUserDetailsService(userMgr);
provider.setPasswordEncoder(passwordEncoder);
provider.setMessageSource(messages);
auth.authenticationProvider(provider);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public AuthenticationTokenProcessingFilter authenticationTokenProcessingFilter() throws Exception{
return new AuthenticationTokenProcessingFilter(authenticationManagerBean());
}
#Bean
public FilterSecurityInterceptor myFilterSecurityInterceptor(
AuthenticationManager authenticationManager,
AccessDecisionManager accessDecisionManager,
FilterInvocationSecurityMetadataSource metadataSource){
FilterSecurityInterceptor interceptor = new FilterSecurityInterceptor();
interceptor.setAuthenticationManager(authenticationManager);
interceptor.setAccessDecisionManager(accessDecisionManager);
interceptor.setSecurityMetadataSource(securityMetadataSource);
interceptor.setSecurityMetadataSource(metadataSource);
return interceptor;
}
#Bean
public AccessDecisionManager accessDecisionManager(SiteConfig siteConfig){
URLBasedSecurityExpressionHandler expressionHandler = new URLBasedSecurityExpressionHandler();
expressionHandler.setSiteConfig(siteConfig);
WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
webExpressionVoter.setExpressionHandler(expressionHandler);
return new AffirmativeBased(Lists.newArrayList(
webExpressionVoter,
new RoleVoter(),
new AuthenticatedVoter()
));
}
public PasswordFixingAuthenticationProvider customAuthenticationProvider(PasswordEncoder passwordEncoder, SaltSource saltSource){
PasswordFixingAuthenticationProvider provider = new PasswordFixingAuthenticationProvider();
provider.setUserDetailsService(userMgr);
provider.setPasswordEncoder(passwordEncoder);
provider.setSaltSource(saltSource);
return provider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(sessionRepositoryFilter, ChannelProcessingFilter.class)
.antMatcher("/ws/**")
.exceptionHandling()
.accessDeniedPage("/mobile/403")
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/ws").permitAll()
.antMatchers("/ws/websocket").permitAll()
.antMatchers("/ws/**").denyAll();
.anyRequest().requiresSecure()
;
}
}
===
public class SmartSessionStrategy implements HttpSessionStrategy {
private HttpSessionStrategy browser;
private HttpSessionStrategy api;
private RequestMatcher browserMatcher = null;
public SmartSessionStrategy(){
this.browser = new CookieHttpSessionStrategy();
HeaderHttpSessionStrategy headerSessionStrategy = new HeaderHttpSessionStrategy();
headerSessionStrategy.setHeaderName(CustomSessionRepositoryMessageInterceptor.SPRING_SESSION_ID_ATTR_NAME);
this.api = headerSessionStrategy;
}
#Override
public String getRequestedSessionId(HttpServletRequest request) {
return getStrategy(request).getRequestedSessionId(request);
}
#Override
public void onNewSession(Session session, HttpServletRequest request, HttpServletResponse response) {
getStrategy(request).onNewSession(session, request, response);
}
#Override
public void onInvalidateSession(HttpServletRequest request, HttpServletResponse response) {
getStrategy(request).onInvalidateSession(request, response);
}
private HttpSessionStrategy getStrategy(HttpServletRequest request) {
if(this.browserMatcher != null)
return this.browserMatcher.matches(request) ? this.browser : this.api;
return SecurityRequestUtils.isApiRequest(request) ? this.api : this.browser;
}
}
I think the question is based on invalid expectations to begin with. You cannot pass the session id and it's not meant to be passed in. You cannot login at the STOMP protocol level, it's not how it it's designed to work.
Although the STOMP protocol does allow for user credentials to be passed in the CONNECT frame that's more useful with STOMP over TCP. In an HTTP scenario we already have authentication and authorization mechanisms in place to rely on. By the time you get to the STOMP CONNECT, you would have had to pass authentication and authorization for the WebSocket handshake URL.
I would start with the Spring reference documentation on Authentication for STOMP/WebSocket messaging if you haven't read that already:
When a WebSocket handshake is made and a new WebSocket session is
created, Spring’s WebSocket support automatically propagates the
java.security.Principal from the HTTP request to the WebSocket
session. After that every message flowing through the application on
that WebSocket session is enriched with the user information. It’s
present in the message as a header.
In other words authentication is the same as for existing web applications. The URL at which the WebSocket endpoint is exposed is just another HTTP endpoint of the application. The same way all other HTTP endpoints are secured is the way the WebSocket handshake is secured. Just like for other HTTP endpoints you don't pass the session id. Instead you're within an existing HTTP session maintained through a cookie.
The handshake cannot be established unless Spring Security authenticates and authorizes the HTTP URL first. From there the STOMP session will pick up the authenticated user and Spring Security offers further ways to authorize individual STOMP messages.
That should all work seamlessly. There is no need to login via STOMP or to pass the Spring Session id at any time.