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
- Use Spring Initializr to Download Initial Project
- Just Jib it!
- Add a Web Controller and Configure Security
- Try it Out!
- Create an OIDC Application
- Configure Spring Boot App for OAuth
- Run the App Again, with OAuth 2.0!
- Learn More about Docker and Spring Boot
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. Open the site in a new window or tab. Fill in your client ID, and use https://{yourOktaDomain}/oauth2/default/v1/authorize
for the Authorize URI. Select code for the response type and Use PKCE.
Scroll down and click Send Request.
Copy the access 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.
- How to Docker with Spring Boot
- Java Microservices with Spring Boot and Spring Cloud
- Spring Method Security with PreAuthorize
- Build a Simple CRUD App with Spring Boot and Vue.js
- A Quick Guide to Spring Boot Login Options
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:
- Apr 17, 2021: Updated to Spring Boot 2.4.5 and streamline setup with the Okta CLI. See changes to this post in okta-blog#731; example app changes can be viewed in okta-spring-boot-docker-example#3.
Okta Developer Blog Comment Policy
We welcome relevant and respectful comments. Off-topic comments may be removed.