Build a Spring Boot App with User Authentication

Spring Boot makes it fun and easy to build rich Java webapps. It allows you to rapidly develop, test, run and deploy Spring applications. Plus with over 100 starters, Spring Boot provides a huge amount of out-of-the-box functionality that traditionally you had to build yourself.

While Spring Boot makes it easier to build Java applications, authentication and authorization protocols can still be one of the biggest pain points for any application developer. Good news though! This 15-minute tutorial shows you how to build a fully-operational Spring Boot webapp that protects user access to restricted paths with Spring Security and Stormpath.

You never have to build auth again with Stormpath's Spring Boot integration, which offers out-of-the-box identity management that you can implement in minutes. You'll see how easy it is to roll out a simple Spring Boot web application, with a complete user registration and login system, with these features:

  • Login and registration pages
  • Password reset workflows
  • Access control based on Group membership
  • Additional configurable features from the Stormpath Java library, including API authentication, SSO, social login, and more

This demo uses the stormpath-default-spring-boot-starter. The modular design of this starter encompasses Spring Boot 1.3.6 and Spring Security 4.1.1 as well as Spring Boot WebMVC, and the Thymeleaf templating engine. I will be using my Mac, the Terminal app, and the IntelliJ IDE.

Grab the code for this tutorial here, and follow along!

Also, throughout this post you can see the example code in action by clicking on the Deploy to Heroku button. All you need to do is register for a free Heroku account.

Get Stormpath

Stormpath is an Identity Management API that enables you to:

  • Authenticate and authorize your users
  • Store data about your users
  • Perform password and social based login
  • Send password reset messages
  • Issue API keys for API-based web apps
  • And much more! Check out detailed Product Documentation

In short, Stormpath makes user account management a lot easier, more secure, and more scalable.

The first step in this tutorial is to register for a free developer account here, if you don't already have one!

Set up Your Spring Boot Project

Whether you are a Maven maven or Gradle great, getting your project setup is a snap.

For Maven, start with this pom.xml file:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.stormpath.sample</groupId>
    <artifactId>stormpath-spring-boot-spring-security-tutorial</artifactId>
    <version>0.1.0</version>
    <name>Spring Boot Spring Security Stormpath Tutorial</name>
    <description>A simple Spring Boot Web MVC application with Spring Security and out-of-the-box login and self-service screens!</description>

    <dependencies>
        <dependency>
            <groupId>com.stormpath.spring</groupId>
            <artifactId>stormpath-default-spring-boot-starter</artifactId>
            <version>1.0.RC9.2</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>1.3.6.RELEASE</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

Or, here's a build.gradle file, if you prefer:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.6.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'spring-boot'

group = 'com.stormpath'
version = '0.1.0'

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'com.stormpath.spring', name: 'stormpath-default-spring-boot-starter', version:'1.0.RC9.2'
}

You may notice that we're working with a single dependency, regardless of whether you're using Maven or Gradle: stormpath-default-spring-boot-starter.

That one dependency gives you all of the Spring Boot, Spring Security and Stormpath magic at once, which we'll demonstrate as we progress through the tutorial.

Gather Your API Credentials and Application href

The connection between your webapp and Stormpath is secured with an API key pair which your webapp will use when it communicates with Stormpath. Download your API key pair (it'll be the apiKey.properties file) from the Admin Console.

Also in the Admin Console, you'll want to get the href for your default Stormpath Application. In Stormpath, an Application object is used to link your web app to your user stores inside Stormpath. All new developer accounts have an app called "My Application." Click on "Applications" in the Admin Console, then click on "My Application." On that page you will see the href for the Application. Copy this -- we will need it later.

Write the Spring Boot Application

The code for this section can be found in the LockedDown tag of the code repo. Deploy

We need three small Java classes and an html template to fire up the first version of our webapp. Let's get to it!

Spring Boot Application Entry Point

All Spring Boot applications have an entry point that works just like an ordinary Java program, main method and everything.

Here's Application.java:

@SpringBootApplication
public class Application  {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

It's just six little lines of code, including the @SpringBootApplication annotation that kicks off the party.

Spring Security Configuration

Spring Security's default is to lock down your entire application. While this conforms to industry-standard best security practices, it's not terribly useful by itself. Additionally, we need to hook Spring Security and Stormpath together. That brings us to our SpringSecurityWebAppConfig.java:

@Configuration
public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.apply(stormpath());
    }
}

The @Configuration annotation causes Spring Boot to instantiate this class as a configuration and .apply(stormpath()) hooks all of the Stormpath authentication and authorization workflows into Spring Security.

Because there is no further configuration in the configure method, we maintain the best practice behavior of having everything locked down. However, instead of the default Spring Security authentication flows, we will see the default Stormpath flows. Attempting to browse to any path in the application will result in a redirect to the Stormpath login page.

Yep, you saw it happen. A one-line method call and we've got security!

Tie It All Together With Spring WebMVC

Our security configuration above ensures that all paths in the application will be secured.

A Controller determines how requested paths get directed to display which templates.

Here's our HelloController.java:

@Controller
public class HelloController {
    @RequestMapping("/")
    String home() {
        return "home";
    }
}

The @Controller annotation signals Spring Boot that this is a controller. We have one path defined on line 3, /. Line 5 returns the Thymeleaf template named home. Welcome to MVC routing!

Bring Us home.html

By default, the Thymeleaf templating engine will look for templates returned from controllers in a folder called templates in your classpath. It will also look for the default extension of .html.

When when the controller we just built returns "home", Thymeleaf will find the template in resources/templates/home.html.

Let's take a look at the home.html file:

<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <!--/*/ <th:block th:include="fragments/head :: head"/> /*/-->
    </head>
    <body>
        <div class="container-fluid">
            <div class="row">
                <div class="box col-md-6 col-md-offset-3">
                    <div class="stormpath-header">
                        <img src="http://stormpath.com/images/template/logo-nav.png"/>
                    </div>

                    <h1 th:inline="text">Hello, [[${account.fullName}]]!</h1>
                    <a th:href="@{/logout}" class="btn btn-danger">Logout</a>
                </div>
            </div>
        </div>
    </body>
</html>

Line 1 sets up the th namespace for Thymeleaf.

Line 3 looks like an html/xml comment. However, this is a directive that Thymeleaf picks up on to include a fragment in this template. The fragment is found at: resources/templates/fragments/head.html and contains all the setup needed to hook in Bootstrap styling for our views.

Lines 13 and 14 are where the action happens. Since every pathway in our application is locked down, we know that we can only access this page post-login. Part of the Stormpath magic is that once logged in, an account object is always in scope to your views. Line 13 shows the logged in user's full name. Line 14 provides a link to log out when clicked.

Fire up Your Spring Boot Application

So that's 1 Stormpath account, 15 lines of Java code, and 19 lines of html template code (3 of which are significant) to bring us to the point of a fully functional Spring Boot WebMVC app protected by Spring Security and backed by Stormpath.

Wow.

If you stored your apiKey.properties file from before in the standard location: ~/.stormpath/apiKey.properties and if you have only the default Stormpath Application that was created for you, no other configuration is necessary to start up the application.

Here's the Maven way:

mvn clean package
mvn spring-boot:run

Note: The spring-boot-maven-plugin also creates an uber-jar due to the presence of the repackage execution. You can exercise the same code by just running Java:

mvn clean package
java -jar target/*.jar

And, here's the Gradle way:

gradle clean build
java -jar build/libs/*.jar

You can browse to http://localhost:8080/ and see it in action:

spring security basic

But wait! What if you stored your apiKey.properties file somewhere else or you have other Stormpath Applications defined? Totally not a problem!

Remember that Application href you saved earlier? Go grab it.

Maven:

mvn clean package
STORMPATH_API_KEY_FILE=~/.stormpath/apiKey.properties \
STORMPATH_APPLICATION_HREF=https://api.stormpath.com/v1/applications/4YHCurbtcaFC4TKKsd3AYQ \
mvn spring-boot:run

Gradle:

gradle clean build
STORMPATH_API_KEY_FILE=~/.stormpath/apiKey.properties \
STORMPATH_APPLICATION_HREF=https://api.stormpath.com/v1/applications/4YHCurbtcaFC4TKKsd3AYQ \
java -jar build/libs/*.jar

By adding STORMPATH_API_KEY_FILE and STORMPATH_APPLICATION_HREF environment variables to the command line, we can easily tell our app where to find the API key pairs and which Stormpath Application to use.

The Stormpath Java SDK has an extremely flexible configuration mechanism. We will see more of that below when we get to restricting access to your application by Group membership.

Refined Access Control With Spring Security

The code for this section can be found under the BasicAccessControl tag of the code repo.

Deploy

So, in the last section we created a webapp that was locked up tight. Every path, including /, required login.

But, maybe you need a publicly accessible home page. Or some areas of the site that any authenticated user can access and other areas restricted to members based on their Group.

Stormpath makes those kinds of fine-grained controls possible, and simple.

Spring Security: Your Bouncer at the Door

We'll start by allowing public access to the home page. Users will still have to authenticate to access any another page. Remember our empty SpringSecurityWebAppConfig.java from before? We are going to add a little something to it now:

@Configuration
public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .apply(stormpath()).and()
            .authorizeRequests()
            .antMatchers("/").permitAll();
    }
}

Spring Security provides a fluent interface for providing access rules.

On Lines 5 - 8 above, we are building a rule set for how Spring Security will allow access to our application.

You might state it like this in plain English:

Permit anyone to go to the front door
Ensure that they've authenticated for anything else

The rules we are specifying take precedence before the default behavior of locking everything down.

Let's update our home.html template as well:

<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <!--/*/ <th:block th:include="fragments/head :: head"/> /*/-->
    </head>
    <body>
        <div class="container-fluid">
            <div class="row">
                <div class="box col-md-6 col-md-offset-3">
                    <div class="stormpath-header">
                        <img src="http://stormpath.com/images/template/logo-nav.png"/>
                    </div>

                    <!--/* displayed if account IS NOT null, indicating that the user IS logged in */-->
                    <div th:if="${account}">
                        <h1 th:inline="text">Hello, [[${account.fullName}]]!</h1>
                        <a href="/restricted" class="btn btn-primary">Restricted</a>
                        <a th:href="@{/logout}" class="btn btn-danger">Logout</a>
                    </div>

                    <!--/* displayed if account IS null, indicating that the user IS NOT logged in */-->
                    <div th:unless="${account}">
                        <h1>Who are you?</h1>
                        <a href="/restricted" class="btn btn-primary">Restricted</a>
                    </div>
                </div>
            </div>
        </div>
    </body>
</html>

Notice how we now have two distinct sections. The first starts on line 13 and is displayed if the user is logged in. The second section starting on line 20 is displayed if the user is not logged in.

Here you're seeing Thymeleaf templates in action! Thymeleaf provides very powerful controls for conditionally showing parts of a template.

Before we make any additional changes, let's pause and start up the app.

When you browse to http://localhost:8080, you should see the unauthenticated version of the home template.

restricted

Click the Restricted button, and you'll be redirected to the login form, as expected. After you authenticate, you'll end up at a 404 page, because we haven't defined the restricted page yet.

Defining the restricted page is as easy as adding a route in our controller and creating a template to show. Here's the updated HelloController.java:

@Controller
public class HelloController {

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

    @RequestMapping("/restricted")
    String restricted() {
        return "restricted";
    }
}

And, here's a new restricted.html template:

<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <!--/*/ <th:block th:include="fragments/head :: head"/> /*/-->
    </head>
    <body>
        <div class="container-fluid">
            <div class="row">
                <div class="box col-md-6 col-md-offset-3">
                    <div class="stormpath-header">
                        <img src="http://stormpath.com/images/template/logo-nav.png"/>
                    </div>

                    <h1 th:inline="text">[[${account.fullName}]], You are allowed here.</h1>
                    <a href="/" class="btn btn-primary">Go Home</a>

                </div>
            </div>
        </div>
    </body>
</html>

Notice how we re-use the head fragment to provide Bootstrap styling for this template.

Re-start the app again, and you will get the full experience of the home page changing depending on whether or not you are logged in.

semi restricted

Spring Security Access Control By Group Membership

The code for this section can be found in the GroupAccessControl tag of the code repo.

Deploy

Spring Security provides a set of annotations and a rich expression language for controlling access to methods in your application. Among the most commonly used Spring Security annotations is @PreAuthorize. And, among the most commonly used SpringEL expressions is hasRole.

We integrate with this mechanism, connecting Stormpath Groups to Spring Security roles.

Let's break down this code by adding a new service that restricts access by Group membership. Here's AdminService:

@Service
public class AdminService {
    @PreAuthorize("hasRole(@roles.ADMIN)")
    public boolean ensureAdmin() {
        return true;
    }
}

Line 3 above is the key. The annotation along with the SpringEL expression could be stated in plain English as:

Before this method is even entered,
check to see that user is authenticated and
is a member of the ADMIN group

The check to see that user is authenticated part of this may not be obvious. What's going on is that a @PreAuthorize check can only be done on an authenticated user. Spring Security is smart enough to check that the user is logged in before checking to see if they are a member of the specified group.

Let's dig in to the Spring Expression Language above. Where is Spring Security looking for @roles.ADMIN? The @ symbol is special - it identifies a Java bean, in this case named roles. Defined inside that bean we expect to find a constant named ADMIN.

Ultimately, hasRole needs to be checking against a unique href representing a Stormpath Group. So, our ADMIN constant needs to be a Java string that holds the href to our Stormpath Group used for admin.

To complete this configuration and to make it awesomely dynamic, we need a new class called Roles.java:

@Component
public class Roles {
    public final String ADMIN;

    @Autowired
    public Roles(Environment env) {
        ADMIN = env.getProperty("stormpath.authorized.group.admin");
    }
}

These 9 lines are so amazingly powerful, I'm geeking out over here! Are you seeing it yet?

By annotating this class with @Component on line 1, Spring will instantiate it and expose it as a bean. Guess what the name of the bean is? Spring will take the name of the class and camel-case it to derive the bean name by default. So, the bean name is roles. Sweet!

The @Autowired annotation on line 5 causes the Spring Environment object to be passed into the constructor. Inside the constructor, we have our only opportunity to set ADMIN since it's declared final - a requirement to be able to use it inside the hasRoles clause.

The last piece of the puzzle utilizes some Spring configuration magic. Notice that we are setting the value of ADMIN to whatever the environment property named stormpath.authorized.group.admin is set to. This is standard Spring. If you have a property in your application.properties file with this name, it will be available in the Spring Environment.

Spring also supports the ability to set this as a system environment variable, alleviating the need to have the value - a Stormpath Group href in this case - hardcoded anywhere in your application.

Typically, system environment variables are ALL_CAPS_WITH_WORDS_SEPARATED_BY_UNDERSCORES. Spring automatically converts these system variables into the lowercase.dotted.notation.

Dig this:

STORMPATH_AUTHORIZED_GROUP_ADMIN=https://api.stormpath.com/v1/groups/1wcsYMUZhzytUH5GmdaFXC \
java -jar build/libs/spring-boot-spring-security-tutorial-0.1.0.jar

Behind the scenes, Spring will convert the STORMPATH_AUTHORIZED_GROUP_ADMIN system environment variable to a Spring env variable named stormpath.authorized.group.admin. That will get picked up by our code above.

Now, we need to wire the AdminService to our Controller. Here are the relevant parts of our updated HelloController.java:

@Controller
public class HelloController {

    @Autowired
    AdminService adminService;

    ...

    @RequestMapping("/admin")
    String admin() {
        adminService.ensureAdmin();
        return "admin";
    }
}

AdminService is Autowired in on lines 4 & 5. Notice on line 11, we are calling the adminService.ensureAdmin method. If the logged in user is NOT in the ADMIN group, a 403 (forbidden) response will be generated.

The last bit of code housekeeping to do here is to create an admin.html template. In the code that is associated with this post, there's a simple admin.html template that shows a nicely formatted message confirming that you are, indeed, an admin.

Now, to see this in action, you'll need to do a little bit of Stormpath housekeeping in the admin console.

Here are the steps:

  1. Create a new Application

    applications home

    create application

  2. Create a new Group called "admin" for the Application

    groups home

    create group

  3. Create a new Account in the admin Group

    accounts home

    create account

    accounts home 2

  4. Create another new Account, but NOT in the admin Group

    accounts home

    create account

In the code for this post, I've also included a handy error page so that if you are not in the admin Group you get a nicely formatted page rather than the default 403 page.

Now we're going to start up the application again, only this time we'll use the hrefs found in the Admin Console for the new Application and Group you just created.

With Maven:

mvn clean package
STORMPATH_API_KEY_FILE=~/.stormpath/apiKey.properties \
STORMPATH_APPLICATION_HREF=https://api.stormpath.com/v1/applications/4YHCurbtcaFC4TKKsd3AYQ \
STORMPATH_AUTHORIZED_GROUP_ADMIN=https://api.stormpath.com/v1/groups/1wcsYMUZhzytUH5GmdaFXC \
mvn spring-boot:run

With Gradle:

gradle clean build
STORMPATH_API_KEY_FILE=~/.stormpath/apiKey.properties \
STORMPATH_APPLICATION_HREF=https://api.stormpath.com/v1/applications/4YHCurbtcaFC4TKKsd3AYQ \
STORMPATH_AUTHORIZED_GROUP_ADMIN=https://api.stormpath.com/v1/groups/1wcsYMUZhzytUH5GmdaFXC \
java -jar build/libs/spring-boot-spring-security-tutorial-0.1.0.jar

Browse to the /admin page.

If you log in as the user you created in the Stormpath admin Group (micah+admin@stormpath.com in my case), you will have access to the admin page. If you log in as the user you created that's NOT in the Stormpath admin Group (micah+user@stormpath.com in my case), you will get the forbidden page.

No matter who you log in as, you will have access to the /restricted page.

admin group

Wrapping Up

We've covered a lot of ground here, haven't we? Let's review:

  • We saw how you can protect individual pages as well as protect methods based on membership in a Group.
  • We saw how you can apply Stormpath in your class that extends WebSecurityConfigurerAdapter to define access controls for different paths as well as make use of the Spring Security @PreAuthorize annotation for a finer grain of control.
  • We saw how the Stormpath Java SDK manages environment configuration properties to provide a high degree of flexibility for your application without having to hardcode anything.

There are a ton of additional features in the Java SDK and the Spring Boot integration that I didn't demonstrate in this tutorial. You can walk through a more in-depth Stormpath tutorial here.

If you have questions or want to learn more catch me on Twitter @afitnerd ot drop a line over to support anytime.

Micah Silverman

Micah Silverman is a software developer, author, and speaker based in New York's Long Island.

He's published a book on enterprise Java: Mastering Enterprise JavaBeans 3.0

He writes very inconsistently on his blog about programming, hacking, making and exercise.

He's contributed to a number of open source projects including:

He created a little Chrome extension to generate passwords: Passable

In his spare time, he likes building MAME arcade machines, repairing classic electronic games and riding motorcycles.