Spring SockJs RequestHandler doesn't upgrade connection to 101 - spring-security

Even though this is not described in the Spring documentation, a websocket connect should lead to a connection upgrade response (101 status).
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig<S extends ExpiringSession> extends AbstractSessionWebSocketMessageBrokerConfigurer<S>{
#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 DefaultHandshakeHandler(new TomcatRequestUpgradeStrategy()))
.setAllowedOrigins("*")
.withSockJS()
.setSessionCookieNeeded(false)
;
}
}
However, I get a 200 status with a "Welcome to SockJS" message which is generated by TransportHandlingSockJsService in stead of the WebSocketHttpRequestHandler which would generate the upgrade AFAIK
#Configuration
public class WebSocketSecurity extends AbstractSecurityWebSocketMessageBrokerConfigurer{
#Override
protected boolean sameOriginDisabled() {
return true;
}
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.nullDestMatcher().permitAll()
.simpSubscribeDestMatchers("/user/queue/errors").permitAll()
.simpDestMatchers("/mobile-server/**").hasRole("ENDUSER")
.simpSubscribeDestMatchers("/user/**", "/topic/**").hasRole("ENDUSER")
.anyMessage().denyAll();
}
}
When I change the config to
#Override
public void configureStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/ws")
.setHandshakeHandler(new DefaultHandshakeHandler(new TomcatRequestUpgradeStrategy()))
.setAllowedOrigins("*");
}
to my surprise a call to /ws does lead to a connection upgrade 101. I'm surprised, since the documentation and all examples uniformly use the withSockJS() and the start of any websocket connection AFAIK is a request upgrade.
I can choose to force the upgrade by connecting to /ws/websocket (also not documented). So, I'm not sure what is best.
Any suggestions?

This is expected behavior. It's how the SockJS protocol works:
http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html. There is an initial "greeting" request and then the client starts trying transports one at a time.

Related

Dependency Injection in Apache Storm topology

Little background: I am working on a topology using Apache Storm, I thought why not use dependency injection in it, but I was not sure how it will behave on cluster environment when topology deployed to cluster. I started looking for answers on if DI is good option to use in Storm topologies, I came across some threads about Apache Spark where it was mentioned serialization is going to be problem and saw some responses for apache storm along the same lines. So finally I decided to write a sample topology with google guice to see what happens.
I wrote a sample topology with two bolts, and used google guice to injects dependencies. First bolt emits a tick tuple, then first bolt creates message, bolt prints the message on log and call some classes which does the same. Then this message is emitted to second bolt and same printing logic there as well.
First Bolt
public class FirstBolt extends BaseRichBolt {
private OutputCollector collector;
private static int count = 0;
private FirstInjectClass firstInjectClass;
#Override
public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {
collector = outputCollector;
Injector injector = Guice.createInjector(new Module());
firstInjectClass = injector.getInstance(FirstInjectClass.class);
}
#Override
public void execute(Tuple tuple) {
count++;
String message = "Message count "+count;
firstInjectClass.printMessage(message);
log.error(message);
collector.emit("TO_SECOND_BOLT", new Values(message));
collector.ack(tuple);
}
#Override
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
outputFieldsDeclarer.declareStream("TO_SECOND_BOLT", new Fields("MESSAGE"));
}
#Override
public Map<String, Object> getComponentConfiguration() {
Config conf = new Config();
conf.put(Config.TOPOLOGY_TICK_TUPLE_FREQ_SECS, 10);
return conf;
}
}
Second Bolt
public class SecondBolt extends BaseRichBolt {
private OutputCollector collector;
private SecondInjectClass secondInjectClass;
#Override
public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {
collector = outputCollector;
Injector injector = Guice.createInjector(new Module());
secondInjectClass = injector.getInstance(SecondInjectClass.class);
}
#Override
public void execute(Tuple tuple) {
String message = (String) tuple.getValue(0);
secondInjectClass.printMessage(message);
log.error("SecondBolt {}",message);
collector.ack(tuple);
}
#Override
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
}
}
Class in which dependencies are injected
public class FirstInjectClass {
FirstInterface firstInterface;
private final String prepend = "FirstInjectClass";
#Inject
public FirstInjectClass(FirstInterface firstInterface) {
this.firstInterface = firstInterface;
}
public void printMessage(String message){
log.error("{} {}", prepend, message);
firstInterface.printMethod(message);
}
}
Interface used for binding
public interface FirstInterface {
void printMethod(String message);
}
Implementation of interface
public class FirstInterfaceImpl implements FirstInterface{
private final String prepend = "FirstInterfaceImpl";
public void printMethod(String message){
log.error("{} {}", prepend, message);
}
}
Same way another class that receives dependency via DI
public class SecondInjectClass {
SecondInterface secondInterface;
private final String prepend = "SecondInjectClass";
#Inject
public SecondInjectClass(SecondInterface secondInterface) {
this.secondInterface = secondInterface;
}
public void printMessage(String message){
log.error("{} {}", prepend, message);
secondInterface.printMethod(message);
}
}
another interface for binding
public interface SecondInterface {
void printMethod(String message);
}
implementation of second interface
public class SecondInterfaceImpl implements SecondInterface{
private final String prepend = "SecondInterfaceImpl";
public void printMethod(String message){
log.error("{} {}", prepend, message);
}
}
Module Class
public class Module extends AbstractModule {
#Override
protected void configure() {
bind(FirstInterface.class).to(FirstInterfaceImpl.class);
bind(SecondInterface.class).to(SecondInterfaceImpl.class);
}
}
Nothing fancy here, just two bolts and couple of classes for DI. I deployed it on server and it works just fine. The catch/problem though is that I have to initialize Injector in each bolt which makes me question what is side effect of it going to be?
This implementation is simple, just 2 bolts.. what if I have more bolts? what impact it would create on topology if I have to initialize Injector in all bolts?
If I try to initialize Injector outside prepare method I get error for serialization.

Spring AMQP and messages in queue

In a Spring AMQP project, I would like to get the number of messages in a certain queue (to make decisions based on that number of messages) in RabbitMQ in real time (I can't use the management plugin).
The basic configuration is this:
#Bean(name="managementServerHandler")
public ManagementServerHandler managementServerHandler(){
return new ManagementServerHandler();
}
#Bean
public MessageListenerAdapter broadcastManagementServerHandler() {
return new MessageListenerAdapter(managementServerHandler(), "handleMessage");
}
#Bean(name="broadcastManagementMessageListenerContainer")
public SimpleMessageListenerContainer broadcastManagementMessageListenerContainer()
{
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(_connectionFactory());
container.setQueueNames( REQUEST_MANAGEMENT_QUEUE );
container.setMessageListener(broadcastManagementServerHandler());
container.setAcknowledgeMode(AcknowledgeMode.AUTO);
container.setAutoDeclare(true);
container.setAutoStartup(true);
container.setConcurrentConsumers(1);
container.setRabbitAdmin((RabbitAdmin)_amqpAdmin());
container.setPrefetchCount(50);
container.setDeclarationRetries(3);
container.setMissingQueuesFatal(true);
container.setFailedDeclarationRetryInterval(1000);
container.setRecoveryInterval(400);
return container;
}
Where the "ManagementServerHandler" is just:
public class ManagementServerHandler implements ServletContextAware, MessageListener
{
#Override
public void onMessage(Message msg)
{....}
}
I need the number of queued messages in the onMessage method, but I can't find the way to do it.
I asked this question, but I don't know how to get the AMQP channel:
RabbitMQ and queue data
Thanks!
Use RabbitAdmin.getQueueProperties(queue)
/**
* Returns 3 properties {#link #QUEUE_NAME}, {#link #QUEUE_MESSAGE_COUNT},
* {#link #QUEUE_CONSUMER_COUNT}, or null if the queue doesn't exist.
*/
#Override
public Properties getQueueProperties(final String queueName) {

Very slow performance of spring-amqp comsumer

I've been experiencing troubles with spring-boot consumer. I compared the work of two consumers.
First consumer:
import com.rabbitmq.client.*;
import java.io.IOException;
public class Recv {
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
Consumer consumer = new DefaultConsumer(channel) {
#Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
}
};
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
Second consumer:
#Controller
public class Consumer {
#RabbitListener(queues = "hello")
public void processMessage(Message message) {
}
}
There are no config files for spring-boot consumer installed, everything goes by default.
On my computer first one works 10 times faster. What might be the problem?
The default prefetch (basicQos) for Spring AMQP consumers is 1 which means only 1 message is outstanding at the consumer at any one time; configure the rabbitListenerContainerFactory #Bean to set the prefetchCount to something larger.
You will have to override the default boot-configured #Bean.

Queues not recreated after broker failure

I'm using Spring-AMQP-rabbit in one of applications which acts as a message-consumer. The queues are created and subscribed to the exchange at startup.
My problem:
When the RabbitMq server is restarted or removed and added completely, the Queue's are not recreated. The connection to the RabbitMq server is re-stored, but not the Queues.
I've tried to do the queue admin within a ConnectionListener but that hangs on startup. I guess the admin is connection aware and should do queue management upon connection restore isn't?
My Queues are created by a service:
#Lazy
#Service
public class AMQPEventSubscriber implements EventSubscriber {
private final ConnectionFactory mConnectionFactory;
private final AmqpAdmin mAmqpAdmin;
#Autowired
public AMQPEventSubscriber(final AmqpAdmin amqpAdmin,
final ConnectionFactory connectionFactory,
final ObjectMapper objectMapper) {
mConnectionFactory = connectionFactory;
mAmqpAdmin = amqpAdmin;
mObjectMapper = objectMapper;
}
#Override
public <T extends DomainEvent<?>> void subscribe(final Class<T> topic, final EventHandler<T> handler) {
final EventName topicName = topic.getAnnotation(EventName.class);
if (topicName != null) {
final MessageListenerAdapter adapter = new MessageListenerAdapter(handler, "handleEvent");
final Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
converter.setJsonObjectMapper(mObjectMapper);
adapter.setMessageConverter(converter);
final Queue queue = new Queue(handler.getId(), true, false, false, QUEUE_ARGS);
mAmqpAdmin.declareQueue(queue);
final Binding binding = BindingBuilder.bind(queue).to(Constants.DOMAIN_EVENT_TOPIC).with(topicName.value());
mAmqpAdmin.declareBinding(binding);
final SimpleMessageListenerContainer listener = new SimpleMessageListenerContainer(mConnectionFactory);
listener.setQueues(queue);
listener.setMessageListener(adapter);
listener.start();
} else {
throw new IllegalArgumentException("subscribed Event type has no exchange key!");
}
}
}
Part of my handler app:
#Component
public class FooEventHandler implements EventHandler<FooEvent> {
private final UserCallbackMessenger mUserCallbackMessenger;
private final HorseTeamPager mHorseTeamPager;
#Autowired
public FooEventHandler(final EventSubscriber subscriber) {
subscriber.subscribe(FooEvent.class, this);
}
#Override
public void handleEvent(final FooEvent event) {
// do stuff
}
}
I wonder why out-of-the-box feature with the RabbitAdmin and beans for Broker entities doesn't fit your requirements:
A further benefit of doing the auto declarations in a listener is that if the connection is dropped for any reason (e.g. broker death, network glitch, etc.) they will be applied again the next time they are needed.
See more info in the Reference Manual.

Oauth2 bad credentials Spring Boot

I am getting the error:
"error": "invalid_grant",
"error_description": "Bad credentials"
Here is the request which I make:
POST /oauth/token HTTP/1.1
Host: localhost:8443
Authorization: Basic bW9iaWxlOg==
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
username=admin&password=pass&client_id=mobile&grant_type=password&client_secret=
My code is from here: https://github.com/juleswhite/mobilecloud-14/tree/master/examples/9-VideoServiceWithOauth2
Here is the code:
Application.java:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import com.capstone.auth.OAuth2SecurityConfiguration;
import com.google.common.io.BaseEncoding;
#Configuration
#EnableAutoConfiguration
#ComponentScan
#EnableWebMvc
#Import(OAuth2SecurityConfiguration.class)
public class Application extends RepositoryRestMvcConfiguration{
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
}
}
ClientAndUserDetailsService.java
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService;
/**
* A class that combines a UserDetailsService and ClientDetailsService
* into a single object.
*
* #author jules
*
*/
public class ClientAndUserDetailsService implements UserDetailsService,
ClientDetailsService {
private final ClientDetailsService clients_;
private final UserDetailsService users_;
private final ClientDetailsUserDetailsService clientDetailsWrapper_;
public ClientAndUserDetailsService(ClientDetailsService clients,
UserDetailsService users) {
super();
clients_ = clients;
users_ = users;
clientDetailsWrapper_ = new ClientDetailsUserDetailsService(clients_);
}
#Override
public ClientDetails loadClientByClientId(String clientId)
throws ClientRegistrationException {
return clients_.loadClientByClientId(clientId);
}
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
UserDetails user = null;
try{
user = users_.loadUserByUsername(username);
}catch(UsernameNotFoundException e){
user = clientDetailsWrapper_.loadUserByUsername(username);
}
return user;
}
}
OAuth2SecurityConfiguration.java
#Configuration
public class OAuth2SecurityConfiguration {
// This first section of the configuration just makes sure that Spring
// Security picks
// up the UserDetailsService that we create below.
#Configuration
#EnableWebSecurity
protected static class WebSecurityConfiguration extends
WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
protected void registerAuthentication(
final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
/**
* This method is used to configure who is allowed to access which parts of
* our resource server (i.e. the "/video" endpoint)
*/
#Configuration
#EnableResourceServer
protected static class ResourceServer extends
ResourceServerConfigurerAdapter {
// This method configures the OAuth scopes required by clients to access
// all of the paths in the video service.
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().antMatchers("/oauth/token").anonymous();
// If you were going to reuse this class in another
// application, this is one of the key sections that you
// would want to change
// Require all GET requests to have client "read" scope
http.authorizeRequests().antMatchers(HttpMethod.GET, "/**")
.access("#oauth2.hasScope('read')");
// Require all other requests to have "write" scope
http.authorizeRequests().antMatchers("/**")
.access("#oauth2.hasScope('write')");
}
}
/**
* This class is used to configure how our authorization server (the
* "/oauth/token" endpoint) validates client credentials.
*/
#Configuration
#EnableAuthorizationServer
#Order(Ordered.LOWEST_PRECEDENCE - 100)
protected static class OAuth2Config extends
AuthorizationServerConfigurerAdapter {
// Delegate the processing of Authentication requests to the framework
#Autowired
private AuthenticationManager authenticationManager;
// A data structure used to store both a ClientDetailsService and a
// UserDetailsService
private ClientAndUserDetailsService combinedService_;
/**
*
* This constructor is used to setup the clients and users that will be
* able to login to the system. This is a VERY insecure setup that is
* using hard-coded lists of clients / users / passwords and should
* never be used for anything other than local testing on a machine that
* is not accessible via the Internet. Even if you use this code for
* testing, at the bare minimum, you should consider changing the
* passwords listed below and updating the VideoSvcClientApiTest.
*
* #param auth
* #throws Exception
*/
public OAuth2Config() throws Exception {
// If you were going to reuse this class in another
// application, this is one of the key sections that you
// would want to change
// Create a service that has the credentials for all our clients
ClientDetailsService csvc = new InMemoryClientDetailsServiceBuilder()
// Create a client that has "read" and "write" access to the
// video service
.withClient("mobile")
.authorizedGrantTypes("password")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read", "write")
.resourceIds("test")
.and()
// Create a second client that only has "read" access to the
// video service
.withClient("mobileReader")
.authorizedGrantTypes("password")
.authorities("ROLE_CLIENT").scopes("read")
.resourceIds("test").accessTokenValiditySeconds(3600)
.and().build();
// Create a series of hard-coded users.
UserDetailsService svc = new InMemoryUserDetailsManager(
Arrays.asList(
User.create("admin", "pass", "ADMIN", "USER"),
User.create("user0", "pass", "USER"),
User.create("username", "password", "USER")));
// Since clients have to use BASIC authentication with the client's
// id/secret,
// when sending a request for a password grant, we make each client
// a user
// as well. When the BASIC authentication information is pulled from
// the
// request, this combined UserDetailsService will authenticate that
// the
// client is a valid "user".
combinedService_ = new ClientAndUserDetailsService(csvc, svc);
}
/**
* Return the list of trusted client information to anyone who asks for
* it.
*/
#Bean
public ClientDetailsService clientDetailsService() throws Exception {
return combinedService_;
}
/**
* Return all of our user information to anyone in the framework who
* requests it.
*/
#Bean
public UserDetailsService userDetailsService() {
return combinedService_;
}
/**
* This method tells our AuthorizationServerConfigurerAdapter to use the
* delegated AuthenticationManager to process authentication requests.
*/
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager);
}
/**
* This method tells the AuthorizationServerConfigurerAdapter to use our
* self-defined client details service to authenticate clients with.
*/
#Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.withClientDetails(clientDetailsService());
}
}
// This version uses the Tomcat web container and configures it to
// support HTTPS. The code below performs the configuration of Tomcat
// for HTTPS. Each web container has a different API for configuring
// HTTPS.
//
// The app now requires that you pass the location of the keystore and
// the password for your private key that you would like to setup HTTPS
// with. In Eclipse, you can set these options by going to:
// 1. Run->Run Configurations
// 2. Under Java Applications, select your run configuration for this app
// 3. Open the Arguments tab
// 4. In VM Arguments, provide the following information to use the
// default keystore provided with the sample code:
//
// -Dkeystore.file=src/main/resources/private/keystore
// -Dkeystore.pass=changeit
//
// 5. Note, this keystore is highly insecure! If you want more securtiy, you
// should obtain a real SSL certificate:
//
// http://tomcat.apache.org/tomcat-7.0-doc/ssl-howto.html
//
#Bean
EmbeddedServletContainerCustomizer containerCustomizer(
#Value("${keystore.file:src/main/resources/private/keystore}") String keystoreFile,
#Value("${keystore.pass:changeit}") final String keystorePass)
throws Exception {
// If you were going to reuse this class in another
// application, this is one of the key sections that you
// would want to change
final String absoluteKeystoreFile = new File(keystoreFile)
.getAbsolutePath();
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) container;
tomcat.addConnectorCustomizers(new TomcatConnectorCustomizer() {
#Override
public void customize(Connector connector) {
connector.setPort(8443);
connector.setSecure(true);
connector.setScheme("https");
Http11NioProtocol proto = (Http11NioProtocol) connector
.getProtocolHandler();
proto.setSSLEnabled(true);
proto.setKeystoreFile(absoluteKeystoreFile);
proto.setKeystorePass(keystorePass);
proto.setKeystoreType("JKS");
proto.setKeyAlias("tomcat");
}
});
}
};
}
}
Thanks for your attention and time!
Short answer: you can't #Autowired an AuthenticationManager into a AuthorizationServerConfigurerAdapter if you are using Spring Boot (yet).
Long anwswer: this sample works though because it autowires the AuthenticationManagerBuilder instead, and constructs a lazy-init version of the AuthenticationManager for the token granter to use. With Spring OAuth2 2.0.3 you will have to create that lazy AuthenticationManager yourself (like this:
authenticationManager = new AuthenticationManager() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return auth.getOrBuild().authenticate(authentication);
}
};
With snapshots (or 2.0.4 when it is released) you can just use the new overloaded method in AuthorizationServerConfigurerAdapter.

Resources