How to Build Secure App with Spring Boot, WebSockets and Okta

How to Build Secure App with Spring Boot, WebSockets and Okta

In this article, we discuss how to add user authentication to an application using Spring Boot, WebSockets, and Okta.

Build a Secure App Using Spring Boot and WebSockets

A WebSocket is a bi-directional computing communication channel between a client and server, which is great when the communication is low-latency and high-frequency. Websockets are mainly used in joint, event-driven, or live apps, where the speed of the conventional client-server request-response model doesn’t meet the credentials. Examples include team dashboards and stock trading applications.

To start, let’s take a look at how the WebSocket protocol works and how to deal with messages with STOMP. Then, you’ll develop an app with Spring Boot and WebSockets and secure them with Okta for authentication and access tokens. You’ll also use the WebSocket API to compose a Java/Spring Boot message broker and verify a JavaScript STOMP client during the WebSocket exchange. Finally, we’re going to add some awesome frameworks to the app so that it plays cool music loops.

The WebSocket Protocol and HTTP

The WebSocket protocol, defined in RFC 6455, consists of an opening handshake, followed by basic message framing, all over TCP. Although it is not HTTP, WebSockets works over HTTP and begins with a client HTTP request with an Upgrade header to switch to the WebSocket protocol:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

The response from the server looks like this:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

After the handshake comes the data transfer phase, during which each side can send data frames, or messages. The protocol defines message types binary and text but does not define their contents. However, it does define the mechanism for sub-protocol negotiation, STOMP.

STOMP — Simple Text Oriented Messaging Protocol

STOMP (Simple Text Oriented Messaging Protocol) was born as an alternative to existing open messaging protocols, like AMQP, to enterprise message brokers from scripting languages like Ruby, Python, and Perl with a subset of common message operations.

STOMP enables a simple publish-subscribe interaction over WebSockets and defines SUBSCRIBE and SEND commands with a destination header. These are inspected by the broker for message dispatching.

The STOMP frame contains a command string, header entries, and a body:

COMMAND
header1:value1
header2:value2

Body^@
Spring Support for WebSockets

Happily, for Java developers, Spring supports the WebSocket API, which implements raw WebSockets, WebSocket emulation through SocksJS (when WebSockets are not supported), and publish-subscribe messaging through STOMP. In this tutorial, you will learn how to use the WebSockets API and configure a Spring Boot message broker. Then we will authenticate a JavaScript STOMP client during the WebSocket handshake and implement Okta as an authentication and access token service. Let’s go!

Spring Boot Example App - Sound Looping!

For our example application, let’s create a collaborative sound-looping UI, where all connected users can play and stop a set of music loops. We will use Tone.js and NexusUI and configure a Spring Message Broker Server and JavaScript WebSocket Client. Rather than building authentication and access control yourself, register for an Okta Developer Account. It’s free!

Once you’ve logged in to Okta, go to the Applications section, and create an application:

  • Choose SPA (Single Page Application) and click Next.
  • On the next page, add http://localhost:8080 as a Login redirect URI.
  • Copy the Client ID from the General Settings.
Create the Message Broker Server Application in Spring Boot

Let’s get started with the application skeleton. Create a Spring Boot application with Spring Initializr and add the Okta Spring Boot Starter and WebSocket dependencies.

curl https://start.spring.io/starter.zip -d dependencies=websocket,okta \
-d language=java \
-d type=maven-project \
-d groupId=com.okta.developer \
-d artifactId=java-websockets  \
-d name="Java WebSockets" \
-d description="Demo project of Spring support for Java WebSockets" \
-d packageName=com.okta.developer.websockets \
-o java-websockets.zip

Click the downloaded .zip file to expand it, or use the following command:

unzip java-websockets.zip -d java-websockets

For the built-in broker with authentication to work, add the following additional dependencies to your pom.xml:


   org.springframework.security
   spring-security-messaging

Configure the built-in STOMP broker with a WebSocketBrokerConfig.java class and add the following code to it. The @EnableWebSocketMessageBroker annotation enables WebSocket support in a Spring Boot app.

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketBrokerConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

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

}

In the configuration above, the /looping connection endpoint initiates a WebSocket handshake and the /topic endpoint handles publish-subscribe interactions.

Token-Based Authentication for Server Side Java

NOTE: Spring Security requires authentication performed in the web application to hand off the principal to the WebSocket during the connection. For this example, we will use a different approach and configure Okta authentication to obtain an access token the client will send to the server during the WebSockets handshake. This allows you to have unique sessions in the same browser. If we only used server-side authentication, your browser tabs would share the same session.

First, configure WebSocket security and request authentication for any message. To do this, create a WebSocketSecurityConfig class to extend AbstractSecurityWebSocketMessageBrokerConfigurer. Override the configureInbound() method to require authentication for all requests, and disable the same-origin policy by overriding sameOriginDisabled().

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry;
import org.springframework.security.config.annotation.web.socket.AbstractSecurityWebSocketMessageBrokerConfigurer;

@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {

    @Override
    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
        messages.anyMessage().authenticated();
    }

    @Override
    protected boolean sameOriginDisabled() {
        return true;
    }

}

For token-based authentication with STOMP and WebSockets the server must register a custom authentication interceptor. The interceptor must have precedence over Spring Security filters, so it must be declared in its own configurer with the highest order. Create a WebSocketAuthenticationConfig class with the following code:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

import java.util.List;

@Configuration
@EnableWebSocketMessageBroker
@Order(Ordered.HIGHEST_PRECEDENCE + 99)
public class WebSocketAuthenticationConfig implements WebSocketMessageBrokerConfigurer {

    private static final Logger logger = LoggerFactory.getLogger(WebSocketAuthenticationConfig.class);

    @Autowired
    private JwtDecoder jwtDecoder;

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(new ChannelInterceptor() {
            @Override
            public Message preSend(Message message, MessageChannel channel) {
                StompHeaderAccessor accessor =
                        MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
                if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                    List authorization = accessor.getNativeHeader("X-Authorization");
                    logger.debug("X-Authorization: {}", authorization);

                    String accessToken = authorization.get(0).split(" ")[1];
                    Jwt jwt = jwtDecoder.decode(accessToken);
                    JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
                    Authentication authentication = converter.convert(jwt);
                    accessor.setUser(authentication);
                }
                return message;
            }
        });
    }
}

The JwtDecoder will parse and decode the token. To verify the signature, it will retrieve and cache the signing key from the issuer.

Create a src/main/resources/application.yml to hold your Okta issuer. This endpoint will be used to validate JWTs.

okta:
  oauth2:
    issuer: https://{yourOktaDomain}/oauth2/default

Note: The value you should use in place of https://{yourOktaDomain} can be found on your Okta dashboard in the top right.

Finally, to serve the JavaScript client from the same application, configure Spring Security to allow unauthenticated access to the static resources:

import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@Order(1)
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/index.html", "/webjars/**", "/js/**").permitAll();
    }
}
JavaScript WebSocket Client

For simplicity, let’s create a static HTML index page to act as the client end for the WebSocket interaction. First, add WebJars dependencies for Bootstrap, SocksJS, and STOMP.

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>webjars-locator-core</artifactId>
    <version>0.38</version>
</dependency>
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>sockjs-client</artifactId>
    <version>1.1.2</version>
</dependency>
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>stomp-websocket</artifactId>
    <version>2.3.3</version>
</dependency>
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>bootstrap</artifactId>
    <version>4.3.1</version>
</dependency>
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.4.1</version>
</dependency>

Then, create an index.html page in src/main/resources/static:

<!DOCTYPE html>
<html>
<head>
    <title>Looping</title>
    <script src="https://ok1static.oktacdn.com/assets/js/sdk/okta-auth-js/2.0.1/okta-auth-js.min.js" type="text/javascript"></script>
    <script src="/js/auth.js"></script>
    <link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <script src="/webjars/jquery/jquery.min.js"></script>
    <script src="/webjars/sockjs-client/sockjs.min.js"></script>
    <script src="/webjars/stomp-websocket/stomp.min.js"></script>
    <script src="/js/NexusUI.js"></script>
    <script src="/js/Tone.js"></script>
    <script src="/js/app.js"></script>
    <link href="https://fonts.googleapis.com/css?family=Permanent+Marker&display=swap" rel="stylesheet">
    <style>
        h1 {
            font-family: 'Permanent Marker', cursive;
        }
    </style>
</head>
<body>
<noscript>
<h2 style="color: #ff0000">It seems your browser doesn't support JavaScript! WebSocket relies on JavaScript being enabled. Please enable JavaScript and reload this page!</h2>
</noscript>
<div id="main-content" class="container">
    <div class="row my-2">
        <div class="col-md-12 text-center">
            <button id="connect" class="btn btn-primary" onclick="connect()" type="button">Connect</button>
            <button id="disconnect" class="btn btn-primary" onclick="disconnect()" type="button">Disconnect</button>
        </div>
    </div>
    <div class="row my-5"></div>
    <div class="row my-2 justify-content-md-center">
        <div class="col-md-12 text-center">
            <h1>Loop me in</h1>
        </div>
    </div>
    <div class="row justify-content-md-center my-2">
        <div class="col col-lg-1 col-sm-2">
        </div>
        <div class="col-md-auto text-center">
                <span id="button-1"></span>
                <span id="button-2"></span>
                <span id="button-3"></span>
                <span id="button-4"></span>
                <span id="button-5"></span>
                <span id="button-6"></span>
                <span id="button-7"></span>
                <span id="button-8"></span>
                <span id="button-9"></span>
        </div>
        <div class="col col-lg-1 col-sm-2">
        </div>
    </div>
</div>
<script src="/js/loop-ui.js"></script>
</body>
</html>
JavaScript Client Authentication

First, create a src/main/resources/static/js folder in your project for the JavaScript files.

Add Tone.js to src/main/resources/static/js. Tone.js is a JavaScript framework to create interactive music in the browser; it will be used to play, stop, and restart the music loops. Download Tone.js from Github.

Add NexusUI also to src/main/resources/static/js. NexusUI is a framework for building web audio instruments, such as dials and sliders, in the browser. In this example, we will create simple circular buttons, each one to play a different loop. Download NexusUI from Github.

Add an auth.js script to handle client authentication with the Okta Authentication SDK. Use the Client ID you copied from the SPA application in the Okta developer console, and also your Org URL. If the client has not authenticated, it will be redirected to the Okta login page. After login and redirect to “/”, and the ID token and access token will be parsed from the URL and added to the token manager.

var authClient = new OktaAuth({
  url: 'https://{yourOktaDomain}',
  issuer: 'https://{yourOktaDomain}/oauth2/default',
  clientId: '{yourClientID}',
  redirectUri: 'http://localhost:8080'
});

var accessToken = authClient.tokenManager.get('accessToken')
  .then(accessToken => {
    // If access token exists, output it to the console
    if (accessToken) {
      console.log(`access_token ${accessToken.accessToken}!`);
    // If ID Token isn't found, try to parse it from the current URL
    } else if (location.hash) {
      authClient.token.parseFromUrl().then(function success(res){
        var accessToken = res[0];
        var idToken = res[1];

        authClient.tokenManager.add('accessToken', accessToken);
        authClient.tokenManager.add('idToken', idToken);

        window.location.hash='';
      });
    }
    else {
      // You're not logged in, you need a sessionToken
      authClient.token.getWithRedirect({
        responseType: ['token','id_token']
      });
    }
  });

Create a src/main/resources/static/js/app.js file with the SocksJS client functionality. The connect() function will retrieve the access token from the token manager and set it in a custom header sent for the CONNECT STOMP command. The client inbound channel interceptor on the server will process this header. Once connected, the client subscribes to the /topic/loops channel. For this example, incoming messages contain a button toggle event.

var stompClient = null;

function connect() {
    authClient.tokenManager.get('accessToken').then(function(accessToken){
        if(accessToken){
            var socket = new SockJS('/looping');
            stompClient = Stomp.over(socket);
            stompClient.connect({"X-Authorization": "Bearer " + accessToken.accessToken}, function (frame) {
                console.log('Connected: ' + frame);
                stompClient.subscribe('/topic/loops', function (message) {
                    console.log(loopEvent);
                    var loopEvent = JSON.parse(message.body);
                    console.log(loopEvent);
                    var button = eval(loopEvent.loopId);
                    if (button.state !== loopEvent.value) {
                        button.state = loopEvent.value;
                        if (loopEvent.value === true) {
                            button.player.restart();
                        } else {
                            button.player.stop();
                        }
                    }
                });
            });
        } else {
            console.log("token expired");
        }
    })
}

function sendEvent(loopId, value){
    if (stompClient != null) {
        stompClient.send("/topic/loops", {}, JSON.stringify({'loopId': loopId, 'value': value}));
    }
}

function disconnect() {
    if (stompClient !== null) {
        stompClient.disconnect();
        stompClient = null;
    }
    console.log("Disconnected");
}

So users can interact with the music loops, create a src/main/resources/static/loop-ui.js script for the UI buttons initialization with Tone.js and NexusUI:

var button1 = new Nexus.Button('#button-1',{
  'size': [80,80],
  'mode': 'toggle',
  'state': false
});

button1.colorize("accent", "#FFBE0B");
button1.colorize("fill", "#333");
button1.player = new Tone.Player({"url": "/loops/loop-chill-1.wav", "loop": true, "fadeOut": 1}).toMaster();
button1.on('change', function(v) {
    if (v === true){
        this.player.restart();
    } else {
        this.player.stop();
    }
    sendEvent("button1", v);    
});

var button2 = new Nexus.Button('#button-2',{
  'size': [80,80],
  'mode': 'toggle',
  'state': false
});
button2.colorize("accent", "#FB5607");
button2.colorize("fill", "#333");
button2.player = new Tone.Player({"url": "/loops/loop-drum-1.wav", "loop": true, "fadeOut": 1}).toMaster();
button2.on('change', function(v) {
    if (v === true){
        this.player.restart();
    } else {
        this.player.stop();
    }
    sendEvent("button2", v);
});

In the code above, button1 is set to play /loops/loop-chill-1.wav and button2 will play /loops/loop-drum-1.wav. Optionally, configure the behavior for buttons 3 to 9, each one should play a different loop when toggled on. You can get the loops from the Github repo of this tutorial. To use your own music files, place them in the src/main/resources/static/loops folder. In addition to loop restart and stop, the on change handler will send the toggle event to the loops topic through the server message broker.

Run and Test the Java Application With WebSockets

Run the application with Maven:

./mvnw spring-boot:run

Open two different browser sessions at http://localhost:8080, with developer tools enabled to watch the console for STOMP traces. The app will first redirect to Okta for the login:

User authentication

You can log in with the same account in both browser sessions or use different accounts. After you log in, the UI will load the loop buttons. In each browser, click the Connect button on the top to initiate the WebSocket handshake with the server and subscribe to the “loops” topic.

Example user interface

You should see STOMP commands CONNECT and SUBSCRIBE in the web console:

>>> CONNECT
X-Authorization:Bearer eyJraWQiOiJvSXk...
accept-version:1.1,1.0
heart-beat:10000,10000

<<< CONNECTED
version:1.1
heart-beat:0,0
user-name:0oa14trc2aHwBOide357

Connected: CONNECTED
user-name:0oa14trc2aHwBOide357
heart-beat:0,0
version:1.1

>>> SUBSCRIBE
id:sub-0
destination:/topic/loops

NOTE: In some browsers, you might see a 404 response when the browser attempts to declare the source map Tone.js and NexusUI.js, as they are not present in the local server. You can ignore the error for the test.

Once both browsers have connected to the server with WebSockets, toggle a loop circle button in one browser, and the loop will start playing. The button should also toggle in the second browser when receiving the STOMP MESSAGE command:

<<< MESSAGE
destination:/topic/loops
subscription:sub-0
message-id:iqtb3gvf-0
content-length:33

{"loopId":"button1","value":true}

Congrats! You’ve successfully connected a Spring Boot Application with WebSockets.

Thank you for reading !

How to do internationalization (i18n) in Java 11, Spring Boot, and JavaScript

How to do internationalization (i18n) in Java 11, Spring Boot, and JavaScript

This tutorial will show you how to internationalize a simple Java app, a Spring Boot app with Thymeleaf, and a JavaScript Widget.

What are i18n and l10n? Internationalization (i18n) is the process of making your application capable of rendering its text in multiple languages. Localization (l10n) means your application has been coded in such a way that it meets language, cultural, or other requirements of a particular locale. These requirements can include formats for date, time, and currency, as well as symbols, icons, and colors, among many other things. i18n enables l10n.

Why is i18n and l10n important? Because you want to make your app accessible to as many users as possible! If you’re a native English speaker, you’re spoiled because English is currently the language of business, and many apps offer an English translation. Internationalizing your Java app is relatively straightforward, thanks to built-in mechanisms. Same goes for Spring Boot - it’s there by default!

This tutorial will show you how to internationalize a simple Java app, a Spring Boot app with Thymeleaf, and a JavaScript Widget.


Java i18n with Resource Bundles

A resource bundle is a .properties file that contains keys and values for specific languages. Using resource bundles allows you to make your code locale-independent. To see how this works, create a new directory on your hard drive for this tutorial’s exercises. For example, java-i18n-example. Navigate to this directory from the command line and create a Hello.java file.

public class Hello {
public static void main(String[] args) {
    System.out.println("Hello, World!");
}
}

Run java Hello.java and you should see “Hello, World!” printed to your console.

If you see any error similar to the one below, it’s because you’re using a Java version < 11. JEP 330 is an enhancement in Java 11 that allows you to run a single file of Java source code, without compiling it.

$ java Hello.java
Error: Could not find or load main class Hello.java

You can install Java 11 from AdoptOpenJDK 11 or use SDKMAN!

curl -s "https://get.sdkman.io" | bash

Once you have SDKMAN installed, you can list the available java versions with sdk list java:

$ sdk list java
Available Java Versions
 13.ea.07-open       8.0.202-zulu
 12.ea.31-open       8.0.202-amzn
  • 11.ea.26-open 8.0.202.j9-adpt
    11.0.2-sapmchn 8.0.202.hs-adpt
    11.0.2-zulu 8.0.202-zulufx
  • 11.0.2-open 8.0.201-oracle
    11.0.2.j9-adpt > + 8.0.181-zulu
    11.0.2.hs-adpt 7.0.181-zulu
    11.0.2-zulufx 1.0.0-rc-12-grl
  • 11.0.1-open 1.0.0-rc-11-grl
  • 11.0.0-open 1.0.0-rc-10-grl
    10.0.2-zulu 1.0.0-rc-9-grl
    10.0.2-open 1.0.0-rc-8-grl
    9.0.7-zulu
    9.0.4-open

================================================================================

    • local version
    • installed
      > - currently in use
      ================================================================================

Set up your environment to use the latest version of OpenJDK with the command below:

sdk default java 11.0.2-open

Now you should be able to run your Hello.java as a Java program.

$ java Hello.java
Hello, World!

Look Ma! No compiling needed!! 😃

Create a messages_en_US.properties file in the same directory and add keys + translations for the terms hello and world.

hello=Hello
world=World

Create messages_es.properties and populate it with Spanish translations.

hello=Hola
world=Mundo

Modify Hello.java to use Locale and ResourceBundle to retrieve the translations from these files.

import java.util.Locale;
import java.util.ResourceBundle;

public class Hello {

public static void main(String[] args) {
    String language = "en";
    String country = "US";

    if (args.length == 2) {
        language = args[0];
        country = args[1];
    }

    var locale = new Locale(language, country);
    var messages = ResourceBundle.getBundle("messages", locale);

    System.out.print(messages.getString("hello") + " ");
    System.out.println(messages.getString("world"));
}

}

Run your Java program again, and you should see “Hello World”.

$ java Hello.java
Hello World

Improve the parsing of arguments to allow only specifying the language.

if (args.length == 1) {
language = args[0];
} else if (args.length == 2) {
language = args[0];
country = args[1];
}

Run the same command with an es argument and you’ll see a Spanish translation:

$ java Hello.java es
Hola Mundo

Yeehaw! It’s pretty cool that Java has i18n built-in, eh?

Internationalization with Spring Boot and Thymeleaf

Spring Boot has i18n built-in thanks to the Spring Framework and its MessageSource implementations. There’s a ResourceBundleMessageSource that builds on ResourceBundle, as well as a ReloadableResourceBundleMessageSource that should be self-explanatory.

Inject MessageSource into a Spring bean and call getMessage(key, args, locale) to your heart’s content! Using MessageSource will help you on the server, but what about in your UI? Let’s create a quick app to show you how you can add internationalization with Thymeleaf.

Go to start.spring.io and select Web and Thymeleaf as dependencies. Click Generate Project and download the resulting demo.zip file. If you’d rather do it from the command line, you can use HTTPie to do the same thing.

mkdir bootiful-i18n
cd bootiful-i18n
http https://start.spring.io/starter.zip dependencies==web,thymeleaf -d | tar xvz

Open the project in your favorite IDE and create HomeController.java in src/main/java/com/example/demo.

package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

@GetMapping("/")
String home() {
    return "home";
}

}

Create a Thymeleaf template at src/main/resources/templates/home.html that will render the “home” view.

<html xmlns:th="http://www.thymeleaf.org">
<body>
<h1 th:text="#{title}"></h1>
<p th:text="#{message}"></p>
</body>
</html>

Add a messages.properties file in src/main/resources that defines your default language (English in this case).

title=Welcome
message=Hello! I hope you're having a great day.

Add a Spanish translation in the same directory, in a messages_es.properties file.

title=Bienvenida
message=¡Hola! Espero que estas teniendo un gran día. 😃

Spring Boot uses Spring’s LocaleResolver and (by default) its AcceptHeaderLocalResolver implementation. If your browser sends an accept-language header, Spring Boot will try to find messages that match.

To test it out, open Chrome and enter chrome://settings/languages in the address bar. Expand the top “Language” box, click Add languages and search for “Spanish”. Add the option without a country and move it to the top language in your preferences. It should look like the screenshot below when you’re finished.

For Firefox, navigate to about:preferences, scroll down to “Language and Appearance” and click the Choose button next to “Choose your preferred language for displaying pages”. Select Spanish and move it to the top.

Once you have your browser set to return Spanish, start your Spring Boot app with ./mvnw spring-boot:run (or mvnw spring-boot:run if you’re using Windows).

| | Add <defaultGoal>spring-boot:run</defaultGoal> in the <build> section of your pom.xml if you want to only type ./mvnw to start your app. |

Navigate to http://localhost:8080 and you should see a page with Spanish words.


Add the Ability to Change Locales with a URL Parameter

This is a nice setup, but you might want to allow users to set their own language. You might’ve seen this on websites in the wild, where they have a flag that you can click to change to that country’s language. To make this possible in Spring Boot, create a MvcConfigurer class alongside your HomeController.

package com.example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;

@Configuration
public class MvcConfigurer implements WebMvcConfigurer {

@Bean
public LocaleResolver localeResolver() {
    return new CookieLocaleResolver();
}

@Bean
public LocaleChangeInterceptor localeInterceptor() {
    LocaleChangeInterceptor localeInterceptor = new LocaleChangeInterceptor();
    localeInterceptor.setParamName("lang");
    return localeInterceptor;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(localeInterceptor());
}

}

This class uses a CookieLocaleResolver that’s useful for saving the locale preference in a cookie, and defaulting to the accept-language header if none exists.

Restart your server and you should be able to override your browser’s language preference by navigating to http://localhost:8080/?lang=en.

Your language preference will be saved in a cookie, so if you navigate back to http://localhost:8080, the page will render in English. If you quit your browser and restart, you’ll be back to using your browser’s language preference.

Hot Reloading Thymeleaf Templates and Resource Bundles in Spring Boot 2.1

If you’d like to modify your Thymeleaf templates and see those changes immediately when you refresh your browser, you can add Spring Boot’s Developer Tools to your pom.xml.

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>

This is all you need to do if you have your IDE setup to copy resources when you save a file. If you’re not using an IDE, you’ll need to define a property in your application.properties:

spring.thymeleaf.prefix=file:src/main/resources/templates/

To hot-reload changes to your i18n bundles, you’ll need to rebuild your project (for example, by running ./mvnw compile). If you’re using Eclipse, a rebuild and restart should happen automatically for you. If you’re using IntelliJ IDEA, you’ll need to go to your run configuration and change “On frame deactivation” to be Update resources.

See this Stack Overflow answer for more information.

Customize the Language used by Okta’s Sign-In Widget

The last example I’d like to show you is a Spring Boot app with Okta’s embedded Sign-In Widget. The Sign-In Widget is smart enough to render the language based on your browser’s accept-language header.

However, if you want to sync it up with your Spring app’s LocalResolver, you need to do a bit more configuration. Furthermore, you can customize things so it sets the locale from the user’s locale setting in Okta.

To begin, export the custom login example for Spring Boot:

svn export https://github.com/okta/samples-java-spring/trunk/custom-login

| | If you don’t have svn installed, go here and click the Download button. |


Create an OIDC App on Okta

If you already have an Okta Developer account, log in to it. If you don’t, create one at developer.okta.com/signup. After you’re logged in to your Okta dashboard, complete the following steps:

  1. From the Applications page, choose Add Application.
  2. On the Create New Application page, select Web.
  3. Give your app a memorable name, then click Done.

Your settings should look similar to the ones below.

You can specify your issuer (found under API > Authorization Servers), client ID, and client secret in custom-login/src/main/resources/application.yml as follows:

okta:
oauth2:
issuer: https://{yourOktaDomain}/oauth2/default
client-id: {yourClientID}
client-secret: {yourClientSecret}

However, it’s more secure if you store these values in environment variables and keep them out of source control (especially if your code is public).

export OKTA_OAUTH2_ISSUER=https://{yourOktaDomain}/oauth2/default
export OKTA_OAUTH2_CLIENT_ID={yourClientID}
export OKTA_OAUTH2_CLIENT_SECRET={yourClientSecret}

| | I recommend adding the above exports to a .okta.env file in the root of your project and adding *.env to .gitignore. Then run source .okta.env before you start your app. |

After making these changes, you can start the app using ./mvnw. Open your browser to http://localhost:8080, click Login and you should be able to authenticate. If you still have your browser set to use Spanish first, you’ll see that the Sign-In Widget automatically renders in Spanish.

This works because Spring auto-enables AcceptHeaderLocaleResolver.

Add i18n Messages and Sync Locales

It seems like things are working smoothly at this point. However, if you add a LocaleChangeInterceptor, you’ll see that changing the language doesn’t change the widget’s language. To see this in action, create an MvcConfigurer class in custom-login/src/main/java/com/okta/spring/example.

package com.okta.spring.example;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;

@Configuration
public class MvcConfigurer implements WebMvcConfigurer {

@Bean
public LocaleResolver localeResolver() {
    return new CookieLocaleResolver();
}

@Bean
public LocaleChangeInterceptor localeInterceptor() {
    LocaleChangeInterceptor localeInterceptor = new LocaleChangeInterceptor();
    localeInterceptor.setParamName("lang");
    return localeInterceptor;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(localeInterceptor());
}

}

Restart the custom-login app and navigate to http://localhost:8080/?lang=en. If you click the login button, you’ll see that the widget is still rendered in Spanish. To fix this, crack open LoginController and add language as a model attribute. You might notice this determines the language by injecting LocaleResolver in the constructor and using localeResolver.resolveLocale(request).

package com.okta.spring.example.controllers;

import org.springframework.web.servlet.LocaleResolver;
...

@Controller
public class LoginController {

...
private static final String LANGUAGE = "language";

private final OktaOAuth2Properties oktaOAuth2Properties;
private final LocaleResolver localeResolver;

public LoginController(OktaOAuth2Properties oktaOAuth2Properties, LocaleResolver localeResolver) {
    this.oktaOAuth2Properties = oktaOAuth2Properties;
    this.localeResolver = localeResolver;
}

@GetMapping(value = "/custom-login")
public ModelAndView login(HttpServletRequest request,
                          @RequestParam(name = "state", required = false) String state)
                          throws MalformedURLException {

    ...
    mav.addObject(LANGUAGE, localeResolver.resolveLocale(request));

    return mav;
}

...

}

Then modify src/main/resources/templates/login.html and add a config.language setting that reads this value.

config.redirectUri = /[[${redirectUri}]]/ '{redirectUri}';
config.language = /[[${language}]]/ '{language}';

Restart everything, go to http://localhost:8080/?lang=en, click the login button and it should now render in English.


Add Internationalization Bundles for Thymeleaf

To make it a bit more obvious that changing locales is working, create messages.properties in src/main/resources, and specify English translations for keys.

hello=Hello
welcome=Welcome home, {0}!

Create messages_es.properties in the same directory, and provide translations.

hello=Hola
welcome=¡Bienvenido a casa {0}!

Open src/main/resources/templates/home.html and change <p>Hello!</p> to the following:

<p th:text="#{hello}">Hello!</p>

Change the welcome message when the user is authenticated too. The {0} value will be replaced by the arguments passed into the key name.

<p th:text="#{welcome(${#authentication.name})}">Welcome home,
<span th:text="${#authentication.name}">Joe Coder</span>!</p>

Restart Spring Boot, log in, and you should see a welcome message in your chosen locale.

You gotta admit, this is sah-weet! There’s something that tells me it’d be even better if the locale is set from your user attributes in Okta. Let’s make that happen!

Use the User’s Locale from Okta

To set the locale from the user’s information in Okta, create an OidcLocaleResolver class in the same directory as MvcConfigurer.

package com.okta.spring.example;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;

import javax.servlet.http.HttpServletRequest;
import java.util.Locale;

@Configuration
public class OidcLocaleResolver extends CookieLocaleResolver {
private final Logger logger = LoggerFactory.getLogger(OidcLocaleResolver.class);

@Override
public Locale resolveLocale(HttpServletRequest request) {
    SecurityContext securityContext = SecurityContextHolder.getContext();
    if (securityContext.getAuthentication().getPrincipal() instanceof OidcUser) {
        OidcUser user = (OidcUser) securityContext.getAuthentication().getPrincipal();
        logger.info("Setting locale from OidcUser: {}", user.getLocale());
        return Locale.forLanguageTag(user.getLocale());
    } else {
        return request.getLocale();
    }
}

}

Then update MvcConfigurer to use this class:

@Bean
public LocaleResolver localeResolver() {
return new OidcLocaleResolver();
}

Try it out by restarting, navigating to http://localhost:8080/?lang=es, and authenticating. You should land back on your app’s homepage with English (or whatever your user’s locale is) as the language.

Yeehaw! Feels like Friday, doesn’t it?! 😃

i18n in JavaScript with Angular, React, and Vue

In this post, you saw how to internationalize a basic Java program and a Spring Boot app. We barely scratched the service on how to do i18n in JavaScript. The good news is I have an excellent example of i18n for JavaScript apps.

JHipster is powered by Spring Boot and includes localization for many languages on the server and the client. It supports three awesome front-end frameworks: Angular, React, and Vue. It uses the following libraries to lazy-load JSON files with translations on the client. I invite you to check them out if you’re interested in doing i18n in JavaScript (or TypeScript).


Originally published by Matt Raible at https://developer.okta.com

Learn More

Full Stack Web Development with Angular and Spring MVC

Build a Basic App with Spring Boot and JPA using PostgreSQL

Introducing TensorFlow.js: Machine Learning in Javascript

5 Javascript (ES6+) features that you should be using in 2019

ES5 to ESNext — here’s every feature added to JavaScript since 2015

Full Stack Developers: Everything You Need to Know

Build a Simple CRUD App with Spring Boot and Vue.js


Java vs JavaScript: Difference between Java and JavaScript

Java vs JavaScript: Difference between Java and JavaScript

The video on Java vs JavaScript provides you with a short and crisp description of the two programming languages. You will also see the head to head comparison between the two on various aspects and learn the similarities and differences between them.

Java vs JavaScript: Difference between Java and JavaScript 

The video on Java vs JavaScript provides you with a short and crisp description of the two programming languages. You will also see the head to head comparison between the two on various aspects and learn the similarities and differences between them.

Thanks for reading

If you liked this post, please do share/like it with all of your programming buddies!

Follow us on Facebook | Twitter

Further reading

The Complete JavaScript Course 2019: Build Real Projects!

Vue JS 2 - The Complete Guide (incl. Vue Router & Vuex)

JavaScript Bootcamp - Build Real World Applications

The Web Developer Bootcamp

JavaScript Programming Tutorial - Full JavaScript Course for Beginners

New ES2019 Features Every JavaScript Developer Should Know

Best JavaScript Frameworks, Libraries and Tools to Use in 2019

Java Programming Masterclass for Software Developers

Selenium WebDriver with Java -Basics to Advanced+Frameworks

Java In-Depth: Become a Complete Java Engineer!

Top 4 Spring Annotations for Java Developer in 2019

Java Tutorial for Absolute Beginners

100+ Java Interview Questions and Answers In 2019

JavaScript != Java

Lately I've been doing a lot of work with Java and JavaScript through JavaScript Engine and every time I come to a question about the two someone feels the need to answer the question with,&nbsp;<em>"Java and JavaScript are two different languages. They have nothing in common!"</em>&nbsp;or&nbsp;<em>"Just because the word Java is in JavaScript doesn't mean its Java"</em>&nbsp;or&nbsp;<em>"Java != JavaScript"</em>.

Lately I've been doing a lot of work with Java and JavaScript through JavaScript Engine and every time I come to a question about the two someone feels the need to answer the question with, "Java and JavaScript are two different languages. They have nothing in common!" or "Just because the word Java is in JavaScript doesn't mean its Java" or "Java != JavaScript".

Well OK... That doesn't answer the question and it is so irrelevant because you can obviously use Java inside of JavaScript and you can use JavaScript inside of Java so just because someone is asking a question about the two doesn't mean they think they're are the same language.

So my question is how come so many people seem to get worked up over the fact someone is trying to use Java and JavaScript together and will dismiss the question with their condescending phrases instead of offering a solution?

Here is an example,

var SwingGui = JavaImporter(Packages.javax.swing,
                    Packages.javax.swing.event,
                    Packages.javax.swing.border,
                    java.awt.event,
                    java.awt.Point,
                    java.awt.Rectangle,
                    java.awt.Dimension);
...

with (SwingGui) {
var mybutton = new JButton(test);
var mypoint = new Point(10, 10);
var myframe = new JFrame();
...
}

sources:

https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/Scripting_Java

http://docs.oracle.com/javase/7/docs/technotes/guides/scripting/programmer_guide/

http://www.oracle.com/technetwork/articles/javase/scripting-140262.html