Automated tests for Spring Boot WebSocket server

20 05 2017

Developing WebSocket server for your Spring Boot app is fairly simple and well described and documented. However when it comes to making sure that it ‘actually works’ is done manually in most cases.

Below I will show how I do the automated integration tests for Websocket server using Spring’s StompClient. I assume that you are familiar with the idea of WebSockets in Spring. If not, here is a very good article: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html

Source Code

Code of this tutorial is for you to see here: https://github.com/yacekmm/looksok/tree/WebSocketDemo/Spring/WebSocket

System under test: configuration

The demo will be presented on the simpliest WS configuration which consists of one entry point endpoint (`/ws`) and in-memory message broker (under `/queue`):

@Configuration
@EnableWebSocketMessageBroker
public class WsConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) { 
        registry.addEndpoint("/ws");
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/queue");
    }
}

The idea behind the integration test

In the test I’m going to:
– use SpringRunner to start up the whole application with the full context
– Autowire Component that in production will be responsible for sending messages to WebSocket clients
– Build and configure Spring’s StompClient and connect a StompSession to my WebSocket server
send a message over WebSocket and verify if my test client received it

Starting the application for tests

With SpringRunner.class used within jUnit test I start the app context and autowire the WSProxy component (the one that sends messages to WS clients):

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
public class WsConfigIntegrationTest {

    @Value("${local.server.port}")
    private int port;
    @Autowired
    private WsProxy wsProxy;

WsProxy in this demo is a simple component sending message with a SimpMessagingTemplate:

@Component
public class WsProxy {

    private SimpMessagingTemplate messagingTemplate;

    @Autowired
    public WsProxy(SimpMessagingTemplate messagingTemplate) {
        this.messagingTemplate = messagingTemplate;
    }

    public void sendMessage(@RequestParam String clientId,
                            @RequestParam String payload){
        messagingTemplate.convertAndSend("/queue/" + clientId, payload);
    }
}

In this configuration, the url of WS endpoint is:

String wsUrl = "ws://127.0.0.1:" + port + "/ws";

Configuring StompClient and connecting StompSession

Using the StompClient with a minimum configuration:

WebSocketStompClient stompClient = new WebSocketStompClient(new StandardWebSocketClient());
stompClient.setMessageConverter(new StringMessageConverter());

I create StompSession to my WS url:

StompSession stompSession = stompClient.connect(wsUrl, new MyStompSessionHandler()).get();

The connect() method returns a future, but here, in tests, I wait synchronously until this session is ready by calling get() on it to get the session instantly.

Oh, and don’t worry about the MyStompSessionHandler – in this configuration it does nothing, except debug logging on the ‘Connect to WS’ event (just overrides the StompSessionHandlerAdapter)

Now it’s time to subscribe the /queue/my-id Channel within the session:

stompSession.subscribe(
    "/queue/my-id", 
    new MyStompFrameHandler((payload) -> resultKeeper.complete(payload.toString())));

The MyStompFrameHandler class is responsible for handling the incoming message in within the session and completing the CompletableFuture promise that it received as an argument. CompletableFuture is a helper variable needed to test asynchronous code:

CompletableFuture<String> resultKeeper = new CompletableFuture<>();

And the handler uses it as follows:

public class MyStompFrameHandler implements StompFrameHandler {

    private final Consumer<String> frameHandler;

    public MyStompFrameHandler(Consumer<String> frameHandler) {
        this.frameHandler = frameHandler;
    }

    ...

    @Override
    public void handleFrame(StompHeaders headers, Object payload) {
        log.info("received message: {} with headers: {}", payload, headers);
        frameHandler.accept(payload.toString());
    }
}

Sending the message

Message is sent by a WsProxy with SimpMessagingTemplate:

@Component
public class WsProxy {

    private SimpMessagingTemplate messagingTemplate;

    @Autowired
    public WsProxy(SimpMessagingTemplate messagingTemplate) {
        this.messagingTemplate = messagingTemplate;
    }

    public void sendMessage(String clientId, String payload){
        messagingTemplate.convertAndSend("/queue/" + clientId, payload);
    }
}

On some machines it’s also good to wait until the connection is fully established so don’t hesitate to add good old:

Thread.currentThread().sleep(1000);

Testing the result asynchronously

The code in test is async so I pass the Future and wait until it completes with the expected result, or to fail test after timeout on waiting for the response, verifying its body:

assertThat(resultKeeper.get(2, SECONDS)).isEqualTo("test-payload");

That’s it

Now you can run the test, it will start your app, send a message, receive it and verify the contents. Which is everything you need to implement the WebSockets.

Source Code

I’m sure that seeing the source code will make you understand the article better. Grab it from my GitHub: https://github.com/yacekmm/looksok/tree/WebSocketDemo/Spring/WebSocket

Advertisements




Spring MVC application architecture

15 02 2015

Relatively short article explaining fairly complex idea of spring app architecture was published on codebeach:

http://www.codebeach.com/2008/06/spring-mvc-application-architecture.html

View, Controller, Service and Model layer explained in few sentences each. That helped me a lot when beginning Spring development





Spring Boot Security: Custom AuditEvent listener configuration

17 01 2015

Security best practices requires all Authentication related events to be logged in defined format and sometimes event should be handled in special way.

Spring security has its own Security Event log implementation and default repository (in memory repository) If you need to provide your own implementation you need to add custom configuration class.

The class should implement the generic ApplicationListener interface for specified event type. In my example I listen for all events (AbstractAuthenticationEvent type). If you need less, you can specify one of its subclasses only.

Here I only log the event, but you can implement your business logic as required. Event object has a timestamp, remote IP address, user login and event type:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
import org.springframework.security.core.Authentication;

@Configuration
public class AuditEventLogConfiguration implements ApplicationListener<AbstractAuthenticationEvent> {

  private final Logger log = LoggerFactory.getLogger(this.getClass());
  
  @Override
  public void onApplicationEvent(AbstractAuthenticationEvent event) {
    //do whatever you need with your event
    log.info(event.toString());
  }
}

Did I help you?
I manage this blog and share my knowledge for free, sacrificing my time. If you appreciate it and find this information helpful, please consider making a donation in order to keep this page alive and improve quality

Donate Button with Credit Cards

Thank You!





HTML 5 Offline Web Application with Spring Boot

30 11 2014

Think of users using your web application from mobile phones when their internet connection breaks down. Native mobile app would still work and cache all user actions, synchronizing them afterwards. HTML5 web apps can also work offline.

Basically it is done by listing the resource files (html, js, images) that browser should cache immadietly and use the cached version when user redirects to it. If internet connection is working, the browser will return the online server version, else, if user is offline the cached version will be used.

Resources to cache can be defined in html headers or in .manifest file. These two locations are interpreted by all modern browsers.

1. Download the code base

This tutorial is based on the source code from SpringBoot MVC Hello World tutorial. All instructions are performed on this code.

2. Create offline.manifest file

In this file you can select the files and resources that will be cached by the browser and used if user requests to connect with server while being disconnected from the Internet. I will create new html file: offline.html and redirect to it if user is offline. My offline.manifest looks as follows:

CACHE MANIFEST
#v1

CACHE:
offline.html

NETWORK:
*

FALLBACK:
/ /offline.html

And is located under the /src/main/resources/static/offline.manifest.

3. Manifest file explained

The manifest file must begin with CACHE MANIFEST. The CACHE: section lists the files that we need for offline use. The NETWORK: section lists any resources that should not be cached. The FALLBACK: section uses the / character to define a URL pattern. It basically asks “is this page in the cache?” If it finds the page there, it displays it. If not, it shows the user the file specified – offline.html.

4. Create the offline.html page

This page will be displayed when user requests any page while being offline. This is static page – not the Spring template, so i simply put it in the /src/main/resources/static folder, next to the offfline.manifest file. The offline.html page content is basic one:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" 
  xmlns:th="http://www.thymeleaf.org" >
    <head>
        <title>Offline Web Application</title>
    </head>
    <body>
        <h1>You are offline</h1>
    </body>
</html>

5. Request to cache resources

To make use of offline.manifest file I point it in my hello.html and greeting.html page head:

<html xmlns="http://www.w3.org/1999/xhtml" 
  xmlns:th="http://www.thymeleaf.org" 
  manifest="offline.manifest">

This way the browser will download the manifest and cache specified resources. Moreover it will add the hello.html and greeting.html to the cache as well

6. Test it

Run spring boot app, and open the url in the browser. When you are on hello page, the browser will cache files for offline use. In Chrome console this is indicated as:

hello:1 Creating Application Cache with manifest http://localhost:8080/offline.manifest
hello:1 Application Cache Checking event
hello:1 Application Cache Downloading event
hello:1 Application Cache Progress event (0 of 1) http://localhost:8080/offline.html
hello:1 Application Cache Progress event (1 of 1) 
hello:1 Application Cache Cached event

Now stop the Spring Boot app and click the url on th page. Since the server is offline, you will be redirected to cached offline.html page (or the greeting.html if it was previously cached). If you try tu go to any other url, for example: http://localhost:8080/someInvalidUrl – then the offline html page will be displayed.

Note: best to be tested in private / incognito browser mode to prevent unwanted cache.

Get the source Code

Full source code for this tutorial you can get at my GitHub under the SpringBootOfflineWebApp tag: https://github.com/yacekmm/looksok/tree/SpringBootOfflineWebApp.

Did I help you?
I manage this blog and share my knowledge for free, sacrificing my time. If you appreciate it and find this information helpful, please consider making a donation in order to keep this page alive and improve quality

Donate Button with Credit Cards

Thank You!





Spring Boot: SSL/HTTPS for embedded Tomcat

22 11 2014

If your Spring Boot app is running on embedded Tomcat, you need to use the TomcatConnectionCustomizer class to set up the HTTPS in Tomcat.

Get the source code

Source Code for this tutorial is available on my github under the SpringBootHttps tag: https://github.com/yacekmm/looksok/tree/SpringBootHttps

1. Prepare keystore and certificate

First you need to have your certificate. If you already have it, go to point 2., else, follow the step 1 and 2 from this tutorial: https://looksok.wordpress.com/2014/11/16/configure-sslhttps-on-tomcat-with-self-signed-certificate/

2. Put your keystore in defined location

You need to locate your keystore file in path on your machine. On my machine this is:

D:\keystore\server.p12

This path I will use in my app configuration.

3. Customize Tomcat Connection

Create class implementing the TomcatConnectorCustomizer, and override its customize(Connection) method. As you can see, in customize() I set exactly the same properties as in stantalone Tomcat xml configuration (see this post). Note that in class constructor I convert the alias string to lowercase – in keystore only these are allowed.

public class MyTomcatConnectionCustomizer implements TomcatConnectorCustomizer {

  private String absoluteKeystoreFile;
  private String keystorePassword;
  private String keystoreType;
  private String keystoreAlias;

  public MyTomcatConnectionCustomizer(String absoluteKeystoreFile,
      String keystorePassword, String keystoreType, String keystoreAlias) {
    this.absoluteKeystoreFile = absoluteKeystoreFile;
    this.keystorePassword = keystorePassword;
    this.keystoreType = keystoreType;
    this.keystoreAlias = keystoreAlias.toLowerCase();

  }

  @Override
  public void customize(Connector connector) {
    connector.setPort(443);
    connector.setSecure(true);
    connector.setScheme("https");
    
    connector.setAttribute("SSLEnabled", true);
        connector.setAttribute("sslProtocol", "TLS");
        connector.setAttribute("protocol", "org.apache.coyote.http11.Http11Protocol");
        connector.setAttribute("clientAuth", false);
        connector.setAttribute("keystoreFile", absoluteKeystoreFile);
        connector.setAttribute("keystoreType", keystoreType);
        connector.setAttribute("keystorePass", keystorePassword);
        connector.setAttribute("keystoreAlias", keystoreAlias);
        connector.setAttribute("keyPass", keystorePassword);
  }
}

4. Create containerCustomizer Bean

Now to use the TomcatConnectionCustomizer, create the bean as follows:

@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() throws FileNotFoundException {

  final String absoluteKeystoreFile = ResourceUtils.getFile("D:\\keystore\\server.p12").getAbsolutePath();

  final TomcatConnectorCustomizer customizer = new MyTomcatConnectionCustomizer(
      absoluteKeystoreFile, "keyPwd", "PKCS12", "keyalias"); 

  return new EmbeddedServletContainerCustomizer() {

    @Override
    public void customize(ConfigurableEmbeddedServletContainer container) {
      if(container instanceof TomcatEmbeddedServletContainerFactory) {
        TomcatEmbeddedServletContainerFactory containerFactory = (TomcatEmbeddedServletContainerFactory) container;
        containerFactory.addConnectorCustomizers(customizer);
      }
    };
  };
}

5. Test it

Start Spring Boot App and go to the:

https:\\127.0.0.1

Your browser will propably warn you about the untrusted certificate:

Przechwytywanie

Note: Don’t use Self_signed certificates in production! Use it only in test / dev environment

Get the source code

Source Code for this tutorial is available on my github under the SpringBootHttps tag: https://github.com/yacekmm/looksok/tree/SpringBootHttps

Did I help you?
I manage this blog and share my knowledge for free, sacrificing my time. If you appreciate it and find this information helpful, please consider making a donation in order to keep this page alive and improve quality

Donate Button with Credit Cards

Thank You!





Spring Security Tutorial: Authorization and user roles

11 11 2014

User authenticated with username and password can access web pages. The second step is to authorize him – decide whether or not he is authorized to access certain resources or not. Spring supports role based authorization. In this tutorial I will show how to assign users a role and how to authorize them.

Use case scenarios

In simpliest case, you can have one role and allow it to access all of your views, or few roles, each authorized to only a subset of resources. For example you can have users with role ‘USER’ that are app users, and ‘ADMIN’ which have more rights and access to admin resources in your webapp.

Download the source code

Full source code for this tutorial is available for download on my git: https://github.com/yacekmm/looksok/tree/RoleBasedAccessTutorial/Spring/SpringSecurity

1. Get the code base

This tutorial is based on the code developed in my previous tutorial. Download the code or follow the tutorial in: Spring Security: Securing your MVC app with basic login and password authentication.

2. What we have now

In the code base you have a method to authenticate user. There is an Autowired configureGlobal() method in a WebSecurityConfigurerAdapter. In it I use the simplest, default in memory authentication, verifying user’s login, password and assigning him a roleUSER‘:

@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
    auth
      .inMemoryAuthentication()
        .withUser("user").password("userPwd").roles("USER");
  }
}

If you are loking for custom authentication method, take a look at the Spring Security Tutorial: Custom authentication engine.

2. Add user with Admin role

Now we want to add the new user with ADMIN role. This is as simple as duplicating the line in previous snippet. My configureGlobal method now is as follows:

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
  auth
    .inMemoryAuthentication()
      .withUser("user").password("userPwd").roles("USER")
      .and()
      .withUser("admin").password("adminPwd").roles("ADMIN");
}

3. Implement role based access

Now when you have two user roles, you can specify wich role will have access to certain resource. In my app I have the hello page and greeting page. I will permit ADMIN role to both of these resources, and USER role only to hello page. Moreover I will require any request to be authenticated, so user must log in. This is how these rules look like in implementation:

protected void configure(HttpSecurity http) throws Exception{

  http
    .formLogin().permitAll()
  .and()
    .authorizeRequests()
    .antMatchers("/hello").hasRole("USER")
    .antMatchers("/hello", "/greeting").hasRole("ADMIN")
    .anyRequest().authenticated();
}

4. Run App and verify

Start the spring boot app and go to the url:

http://localhost:8080/

You will be automatically redirected to the login page. Login as admin:

admin :  adminPwd

after login you will be redirected to Welcome page (hello), and when you click a link, you will be redirected to the greeting page.

Now logout by going to the login page in your app with logout param:

http://localhost:8080/login?logout

And login as user:

user :  userPwd

After logging in, you will also be redirected to the Welcome page, but when you click th link on that page, you will get the Error Page 403, which means that you are not authorized to access it.

5. Provide custom unauthorized page

The spring’s Whitelabel error page is not the best you can have in your app, so let’s configure new template to have nice, custom unauthorized message for your users. To do it, you just create new html template and controller, let’s name it ‘unauthorized’ and add the exception configuration for HttpSecurity in configure method:

.and()
  .exceptionHandling().accessDeniedPage("/unauthorized");

Download the source code

Full source code for this tutorial is available for download on my git: https://github.com/yacekmm/looksok/tree/RoleBasedAccessTutorial/Spring/SpringSecurity

Did I help you?
I manage this blog and share my knowledge for free, sacrificing my time. If you appreciate it and find this information helpful, please consider making a donation in order to keep this page alive and improve quality

Donate Button with Credit Cards

Thank You!









%d bloggers like this: