Get Jibby With Java, Docker, and Spring Boot

avatar-moksamedia.jpg Andrew Hughes

Docker is a very popular system for containerizing applications. Containerization packages the executable code along with the runtime environment in deployable virtual images using a repeatable, automatable process.

In the world of cloud-based development and microservices, where a single application can be spread across hundreds of servers in various networks with complex port configurations, the ability to automate the deployment of “units” of code is super helpful. The ability to control the execution environment also offers advantages: managing variables like OS version, disk size, available memory, port configuration, etc… Containerization helps avoid unexpected conflicts when OS libraries create unexpected conflicts or bugs on update.

All of this control often comes at the cost of complexity, however. Creating and maintaining dockerfiles can be time-consuming. Jib to the rescue! Jib allows you to easily Dockerize Spring Boot projects, using plugins for Maven and Gradle. Beyond just ease of containerization, Jib also takes advantage of image layering and registry caching to speed up builds by only re-building the parts of an application that have changed.

In this tutorial, you will build a simple Spring Boot REST API and use Jib to dockerize it. You will use OAuth 2.0 and Okta to protect the resource server.

Let’s get started!

Install Dependencies

For this tutorial, you need to install a few dependencies. First, you’ll need Java. I’ve written the tutorial for Java 11, but it should be backward compatible with Java 8. If you don’t have Java installed, go to the AdoptOpenJDK website and install it. On a Mac, you can also use Homebrew.

The next tool you’ll need is HTTPie, a simple command-line HTTP client. Please follow instructions on their website to install it.

You’ll also need a free developer account with Okta. Okta is a SaaS (software-as-service) identity management provider. We make it easy to add features like single sign-on, social login, and OAuth 2.0 to your application. Sign up for an account on our website if you haven’t already.

Finally, you’ll need Docker Desktop. This allows you to quickly and easily run local Docker images on your computer. It’s great for testing, development, and tutorials like this. Check out the Docker Desktop website for installation instructions.

This tutorial uses Gradle as a build system, which you can install locally from their website, but it’s not required to install since the project starter will include a Gradle wrapper. But if you want to install Gradle locally, or just want to learn more about the project, check out the Gradle website.

You’ll also need some sort of code editor or IDE. I like Intellij IDEA Community Edition for Java development. It’s free and awesome. But there are tons of other options as well.

Use Spring Initializr to Download Initial Project

You installed HTTPie, right? In this section, you’re going to use it to command Spring Initializr to create and download your initial project.

From a command line:

http https://start.spring.io/starter.zip \
 dependencies==web,okta \
 groupId==com.okta.spring-docker.demo \
 packageName==com.okta.spring-docker.demo \
 type==gradle-project \
 -d

You can read about all of the parameters available on Spring Initializr’s REST API on the Spring Initializr GitHub page. The important points are that you specified a Gradle project, included a couple of dependencies, and specified your group and package information.

The two dependencies are web and okta. web is short for spring-boot-starter-web, which allows Spring Boot to serve HTTP requests. okta is short for Okta’s Spring Boot Starter, which simplifies adding Okta OAuth to Spring applications. If you’d like to learn more about this project, check out the Okta Spring Boot GitHub page.

The command above downloads a file named demo.zip. Unzip it somewhere and open it in the editor or IDE of your choice:

unzip demo.zip -d spring-boot-docker

This fully functioning Spring Boot app defines an empty Spring Boot application without any controllers, so it doesn’t do much. Before you fix that, you need to add one more dependency to the build.gradle file.

Just Jib it!

To add Jib to the Gradle project, simply add the plugin to the build.gradle file. If you want to dig in deeper, take a look at the Introducing Jib blog post or Jib’s GitHub page.

Add id 'com.google.cloud.tools.jib' version '1.3.0' to the plugins closure at the top of the build.gradle file, like so:

plugins {  
    id 'org.springframework.boot' version '2.1.5.RELEASE'  
    id 'java'  
    id 'com.google.cloud.tools.jib' version '1.3.0'  // <-- ADD ME
}

If you open a shell and navigate to the project root, you can now run ./gradlew tasks and see the tasks that the new plugin has added to the project.

Jib tasks
---------
jib - Builds a container image to a registry.
jibBuildTar - Builds a container image to a tarball.
jibDockerBuild - Builds a container image to a Docker daemon.

In this example, you will be using the jibDockerBuild tasks. This pushes the image to the Docker daemon run by Docker Desktop. This is great for local development and testing.

More often in a larger production environment, you would push to a container registry using jib. Jib can easily push to a variety of container registries, such as Google Container Registry, Amazon Elastic Container Registry, Docker Hub Registry, and Azure Container Registry.

Note that Docker Desktop has to be running in order for the jibDockerBuild task to work. Go ahead and start Docker Desktop if it isn’t already running.

Add a Web Controller and Configure Security

In order for your application to respond to HTTP requests, you need to add a controller. Add a new file called WebController.java in the directory src/main/java/com/okta/springdocker/demo.

package com.okta.springdocker.demo;  
  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.ResponseBody;  
import org.springframework.web.bind.annotation.RestController;  
  
@RestController  
public class WebController {  
  
    @RequestMapping("/")    
    public String home() {  
        return "Welcome!";  
    }  
      
}

You also need to configure the security settings for this project. For the moment, you’ll want to allow all requests, so update your DemoApplication.java file to the following:

package com.okta.springdocker.demo;  
  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.security.config.annotation.web.builders.HttpSecurity;  
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;  
  
@SpringBootApplication  
public class DemoApplication {  
  
   public static void main(String[] args) {  
      SpringApplication.run(DemoApplication.class, args);  
   }  
  
   @Configuration  
   static class OktaOAuth2WebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {  
  
      @Override  
      protected void configure(HttpSecurity http) throws Exception {  
         http  
            .authorizeRequests().anyRequest().permitAll();  
      }  
   }  
}

Try it Out!

Build the project and push the Docker image to the local registry using the following command (from the project root dir):

./gradlew build jibDockerBuild

After this completes, you should be able to list the Docker images:

docker images

And see your application image in the local Docker registry:

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
demo                0.0.1-SNAPSHOT      490d12302a6d        49 years ago        146MB

To run your Spring Boot app, use the following command:

docker run --publish=8080:8080 demo:0.0.1-SNAPSHOT

This command specifies the image repository and tag as well as instructing Docker to map port 8080 in the image to local port 8080.

Now you can use HTTPie to run a simple request:

http :8080

And you should see:

HTTP/1.1 200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Length: 8
Content-Type: text/plain;charset=UTF-8
...

Welcome!

Sweet! So, at this point, you’ve created a simple Spring Boot application with a basic web controller and overridden the default security settings to allow all requests. You’ve also created a Docker image, pushed it to your local Docker registry, and run the image - all without a Docker file!

The next step is to add JSON Web Token (JWT) authentication using OAuth 2.0 and OpenID Connect (OIDC). The provider you’re going to use for this tutorial is Okta.

Create an OIDC Application

Create an OIDC application on Okta using your developer account. If you don’t have one, signup and return to this tutorial after activating your account.

Sign in to the Okta developer console. If this is your first time to log in, you may need to click the Admin button in the upper right-hand corner to get to the developer console.

Next, you will create an OpenID Connect (OIDC) application. OAuth 2.0 along with OpenID Connect is the protocol spec Okta implements to allow your application to handle authentication and authorization securely with the Okta servers.

Click on the Application top menu. Click the Add Application button.

Select application type Web.

Click Next.

Give the app a name. I named mine Spring Boot Docker. The OIDC Debugger website allows you to create access tokens you can use to access your app with HTTPie. You need to add a login redirect URI and allow implicit flow for this website to work.

Under Login redirect URIs, add a new URI: https://oidcdebugger.com/debug.

Under Grant type allowed, check the box next to Implicit (Hybrid).

The rest of the default values will work.

Click Done.

Leave the page open and take note of the Client ID. You’ll need it in a moment.

You’ll also want to know the Issuer URI from Okta. If you go to API in the top menu and click on Authorization Servers, you’ll see the default auth server. By default, all your OIDC apps are added to this auth server. The Issuer URI will be something like this: https://dev-123456.okta.com/oauth2/default.

You won’t need to do anything with it since this tutorial uses the Okta Spring Boot Starter and default values. The audience value, api://default, can be customized for more complex or custom applications.

Configure Spring Boot App for OAuth

First, rename the src/main/resources/application.properties file to application.yml. Then add in the following values (filling in your Client ID and Okta URL):

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

If you were not using the Okta Spring Boot Starter, this configuration would be a little more extensive, but because you’re using the starter, it sets many defaults for you and simplifies setup.

Next, update the security configuration to use OAuth 2.0 and JWT authentication. In the DemoApplication.java file, update the configure(HttpSecurity http) method in the OktaOAuth2WebSecurityConfigurerAdapter static class to match the following:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests().anyRequest().authenticated()
        .and()
        .oauth2ResourceServer().jwt();
}

Finally, change your WebController.java file to match the following:

package com.okta.springdocker.demo;  
  
import org.springframework.security.core.annotation.AuthenticationPrincipal;  
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.ResponseBody;  
import org.springframework.web.bind.annotation.RestController;  
  
import java.security.Principal;  
  
@RestController  
public class WebController {  
  
    @RequestMapping("/")
    public String home(@AuthenticationPrincipal JwtAuthenticationToken jwtAuthenticationToken) {  
        return "Welcome " + jwtAuthenticationToken.getName() + "!";  
    }  
  
    @RequestMapping("/info")
    public String info(@AuthenticationPrincipal JwtAuthenticationToken jwtAuthenticationToken) {  
        return jwtAuthenticationToken.toString();  
    }  
  
}

You could have left the WebController the same. This doesn’t affect authentication. The changes demonstrate how to get a little information about the authenticated party from Spring Security.

Run the App Again, with OAuth 2.0!

Stop the previous process if it’s still running. You should be able to Control-C from the shell where you ran the docker run command. If that doesn’t work, you can use the following command to stop all running docker containers:

docker stop $(docker ps -a -q)

From a terminal at the project root, run the following command to rebuild the project and the docker image:

./gradlew build jibDockerBuild

Once this process completes, run the image:

docker run --publish=8080:8080 demo:0.0.1-SNAPSHOT

Use HTTPie to make a request:

http :8080

You’ll get:

HTTP/1.1 401
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Length: 0
...

Success! Of sorts. You still need to get a valid token. Fortunately, OpenID Connect debugger allows you to do that easily (remember when you added this URL to the list of authorized redirect URLs in your OIDC app on Okta?).

In a browser, go to https://oidcdebugger.com.

Update the following values:

  • Authorize URI: https://{yourOktaDomain}/oauth2/default/v1/authorize
  • Client ID: {yourClientID}
  • State: Any value really, I used This is the state

Scroll down and click Send Request.

Copy the token from the success screen and save it in a shell variable:

TOKEN=eyJraWQiOiJxMm5rZmtwUDR...

Now you can use the JWT in your request:

http :8080 "Authorization: Bearer $TOKEN"

And see something like:

HTTP/1.1 200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Length: 33
...

Welcome andrew.hughes@gmail.com!

If you look back in the WebController class, you can see where we used dependency injection to get the JwtAuthenticationToken, which got the authenticated name:

"Welcome " + jwtAuthenticationToken.getName() + "!";

You can also try the /info endpoint (in the JwtAuthenticationToken class) for more detailed information.

Learn More about Docker and Spring Boot

In this tutorial, you learned how to use Jib to easily Dockerize Spring Boot applications. You also used Okta and OAuth 2.0 / OIDC to protect this application. You generated a JWT using the OIDC Debugger and tested the authentication using the command line.

You can find the source code for this example on GitHub.

Going forward, you could explore how to deploy Spring Boot apps to microservices, how to add Role-based authentication, or how to integrate a Spring Boot REST API with a JavaScript frontend like Vue or Angular.

If you have any questions about this post, please add a comment below. For more awesome content, follow @oktadev on Twitter, like us on Facebook, or subscribe to our YouTube channel.