m4nu56

Tldr

Demonstration of a Java Servlet App and a React SPA running alongside to allow you to migrate progressively towards a modern stack.

You will find the source of the demo project on GitHub: https://github.com/m4nu56/java-servlet-api-react

Demo navigation

Migrate your existing Java Servlet jsp app to a modern stack React SPA + API

In the company I work for we have several historical application that are still in production and actively maintained. Those apps run a Java Servlet environment with a front using JavaServer Pages (JSP).

We're willing to migrate those apps to a more modern a maintainable stack using a simple Java API for the backend, and a React app for the front.

Those apps are monstrous in functionalities, and it's not possible to stop working on the existing app to begin a new one from scratch.

What we will be doing is maintain the existing app in production while working on the migration of new modules on the new stack. Step by step we will redevelop all existing modules into the new stack until one day (hopefully) we will be able to shut down the historical Java Servlet app.

The idea is to be able to navigate between the historical Java Servlet application, and the new React SPA (more or less) seamlessly for the user.

Problematics

Authentication

We previously made an evolution of the authentication solution in all our apps to integrate Keycloak as our Identity and access management authority.

You can read more about how we manage to integrate it in our historical app with this post: Secure your Java Servlet Application with Keycloak

Single Sign On

Keycloak gives us the opportunity to add Single Sign On (SSO) between our different apps, the React SPA (Single Page Application) will be considered as a new Client in our Keycloak configuration so that when logged in the historical app a user won't have to login again in the react app.

User Session and Cookie

The existing app is using HttpSession to store the user connected, the context of the app etc...

We need to be able to access the user session within the API endpoints. To do that both existing Java Servlet App, and the new Java API must be installed on the same domain (but not the same path) and share the same Cookie.

To access the HttpSession corresponding to our connected user we will need to send the same Cookie generated by the Servlet App with our request to the API. The Cookie for accessing the Java Session is named JSESSIONID.

By default, the JSESSIONID Cookie is generated for the path of your app. If you're using Tomcat you can configure this in the META-INF/context.xml configuration file by setting the sessionCookiePath parameter to /.

<Context path="/webapp" sessionCookiePath="/">
...
</Context>

We also need to modify the SessionCookieConfig to modify the Cookie Base Path by /:

public class ContextListener extends HttpServlet implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
// Set des paramètres du cookie
SessionCookieConfig sessionCookieConfig = servletContextEvent.getServletContext().getSessionCookieConfig();
sessionCookieConfig.setPath("/");
}
}

That way the cookie set on the user web browser by the Java Servlet App will be used by the other apps running on the same domain. In your local development configuration this will end up with both:

  • http://localhost:8080 for your Java Servlet App
  • http://localhost:3000 for your React SPA

localhost:8080

localhost:3000

CORS

The API will be requested by our React SPA from another web context, and it will be sending the JSESSIONID Cookie to the API. To do so it's important to specify the URL of your React app in the CORS policy and also to activate the credentials:

In ContainerResponse.java:

"Access-Control-Allow-Origin", "http://localhost:3000" // The path where you will be running the REACT SPA
"Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"
"Access-Control-Expose-Headers", "Location"
"Access-Control-Allow-Credentials", true // Important to allow the client to send Cookie information with its requests

Access the HttpSession in the API endpoint

We can inject the HttpServletRequest using the @Context annotation in our Jersey API endpoint.

As long as the API is being requested using the correct Cookie JSESSIONID we can access the session that was set by the Java Servlet App with a simple request.getSession()

@Path("users")
public class UserApi {
@Context
private HttpServletRequest request;
@GET
@Path("/session/who-am-i")
@Produces(MediaType.APPLICATION_JSON)
public User getUserLoggedInSession() {
return (User) request.getSession().getAttribute("user");
}
@PUT
@Path("/session/update")
@Produces(MediaType.APPLICATION_JSON)
public User updateUserInSession(@QueryParam("login") String login) {
User user = (User) request.getSession().getAttribute("user");
if (user != null) {
user.setLogin(login);
return user;
}
throw new ObjectNotFoundException("User not found in session");
}
}

The React SPA

The module is making a request to the API using the Cookie available for the app domain in the browser.

const request = new window.Request('http://localhost:8080/webapp/api/users/session/who-am-i', {
method: 'GET',
credentials: 'include', // Important so that Cookies are sent with the fetch request
headers: new window.Headers({
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
}),
})
return window.fetch(request)
.then(response => checkStatus(response))
.catch(error => window.Promise.reject(error))
©2020 Emmanuel Balpe. All Rights Reserved. Built with NextJS.