Last Updated: April 17, 2021

Get Jibby With Java, Docker, and Spring Boot

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!

Table of Contents

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 11. 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 bootVersion==2.4.5.RELEASE \
 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 '3.0.0' to the plugins closure at the top of the build.gradle file, like so:

plugins {
    id 'org.springframework.boot' version '2.4.5'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'com.google.cloud.tools.jib' version '3.0.0'  // <-- ADD ME
    id 'java'
}

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        51 years ago        267MB

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.

Stop your app and open a terminal window to its directory.

Create an OIDC Application

Before you begin, you’ll need a free Okta developer account. Install the Okta CLI and run okta register to sign up for a new account. If you already have an account, run okta login. Then, run okta apps create. Select the default app name, or change it as you see fit. Choose Web and press Enter.

Select Okta Spring Boot Starter. Accept the default Redirect URI values provided for you. That is, a Login Redirect of http://localhost:8080/login/oauth2/code/okta and a Logout Redirect of http://localhost:8080.

What does the Okta CLI do?

The Okta CLI will create an OIDC Web App in your Okta Org. It will add the redirect URIs you specified and grant access to the Everyone group. You will see output like the following when it’s finished:

Okta application configuration has been written to: 
  /path/to/app/src/main/resources/application.properties

Open src/main/resources/application.properties to see the issuer and credentials for your app.

okta.oauth2.issuer=https://dev-133337.okta.com/oauth2/default
okta.oauth2.client-id=0oab8eb55Kb9jdMIr5d6
okta.oauth2.client-secret=NEVER-SHOW-SECRETS

NOTE: You can also use the Okta Admin Console to create your app. See Create a Spring Boot App for more information.

Configure Spring Boot App for OAuth

First, confirm your src/main/resources/application.properties has your Okta values in it.

okta.oauth2.issuer=https://{yourOktaDomain}/oauth2/default
okta.oauth2.client-id={yourClientId}
okta.oauth2.client-secret={yourClientSecret}

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:

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.RestController;

@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?).

An easy way to get an access token is to generate one using OpenID Connect Debugger. First, you must configure your application on Okta to use OpenID Connect’s implicit flow.

Run okta login and open the resulting URL in your browser. Go to the Applications section and select the application you just created. Edit its General Settings and add Implicit (Hybrid) as an allowed grant type, with access token enabled. Then, make sure it has https://oidcdebugger.com/debug in its Login redirect URIs. Click Save and copy the client ID for the next step.

Now, navigate to the OpenID Connect Debugger website. Fill in your client ID, and use https://{yourOktaDomain}/oauth2/default/v1/authorize for the Authorize URI. The state field must be filled but can contain any characters. Select token for the response type.

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.

Changelog: