A Simple Web App with Spring Boot, Spring Security and Stormpath – in 15 Minutes
Building Identity Management, including authentication and authorization? Try Stormpath! Our REST API and robust Java SDK support can eliminate your security risk and can be implemented in minutes. Sign up, and never build auth again!
UPDATE: We recently released a revision to our Stormpath Spring Security integration. You no longer have to inherit from a special Stormpath security configurer adapter. Instead, you apply a Stormpath DSL (domain specific language). Look below to see how easy this is.
Here at Stormpath, we ♥ Spring Boot. It makes it so easy and fun to build rich Java webapps.
We’re very excited for our latest Java SDK release which includes a major overhaul to our Spring Security and Spring Boot support.
If you’ve built a web app before, you know that all the “user stuff” is a royal pain. Stormpath gives developers all that “user stuff” out-of-the-box so you can get on with what you really care about – your app! By the time you’re done with this tutorial ( < 15 minutes, I promise), you’ll have a fully-working Spring Boot webapp that protects user access to restricted paths with Spring Security and is backed by Stormpath.
We’ll focus on our Spring Boot integration 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
- Restricting access according to Group membership
- The ability to easily enable other Stormpath features in our Java library (API authentication, SSO, social login, and more)
In this demo we will be using the stormpath-default-spring-boot-starter. Its modular design bakes in Spring Boot 1.3.0 and Spring Security 4.0.3 as well as Spring Boot WebMVC and the Thymeleaf templating engine. I will be using my Mac, the Terminal app, and the IntelliJ IDE.
All the code for this tutorial can be found here.
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.
What Is Stormpath?
Stormpath is an API service that allows developers to create, edit, and securely store
user accounts and user account data, and connect them with one or multiple applications. Our API 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 our Product Documentation
In short: we make user account management a lot easier, more secure, and more
scalable than what you’re probably used to.
Ready to get started? Register for a free developer account here.
Start Your Spring Boot Project
Got your Stormpath developer account? Great! Let’s get started…
Whether you are a Maven maven or Gradle grete, getting your project setup is a snap.
Here’s a pom.xml
file to start with:
<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.RC6</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>1.3.0.RELEASE</version> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
And, here’s a build.gradle
file to start with:
buildscript { repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.0.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.RC6' }
You may notice that both for Maven and Gradle there is a single dependency: stormpath-default-spring-boot-starter
. Yup. That’s it. As you will see below, that one dependency gives you all of the Spring Boot, Spring Security and Stormpath magic at once.
Gather Your API Credentials and Application Href
The connection between your app and Stormpath is secured with an “API Key Pair”. You will provide these keys
to your web app and it will use them when it communicates with Stormpath. You can download your API key pair from our Admin Console. After you log in you can download your API key pair from the homepage, it will download the apiKey.properties
file – we will use this in a moment.
While you are in the Admin Console you 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.
Writing the Spring Boot Application
The code for this section can be found in the LockedDown tag of the code repo.
We need three small Java classes and an html template to fire up the first version of our webapp. They’re small enough that I will put them right here. 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. It has a main
method and everything.
Here’s Application.java
:
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
6 lines of code including the @SpringBootApplication
annotation kicks off the party.
Spring Security Configuration
By default (and by best security practices), Spring Security locks down your entire application. It locks it down to the point that it’s not even accessible! While this conforms to best security practices, it’s not terribly useful. 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..apply(stormpath())
hooks all of the Stormpath goodness (authentication and authorization workflows) into Spring Security.
Because there is no further configuration in the configure
method, we will still see the default behavior of having everything locked down. However, instead of the default Spring Security authentication flows, we will see the default Stormpath flows. That is, attempting to browse to any path in the application will result in a redirect to the Stormpath login
page.
So, a 1 line method call and we’ve got security!
Spring WebMVC Ties It All Together
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
.
7 lines of code and we have Model-View-Controller (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
.
So, when our controller above returns "home"
, Thymeleaf will find the template inresources/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
. This fragment contains all the setup to hook in Bootstrap styling for our views.
Lines 13 and 14 is where the action is. Since every pathway in our application is locked down, we know that the only way to get to this page is after having logged in. 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.
For more information on working with Thymeleaf templates, click here.
Let’s Fire It Up!
Creating a Stormpath account, 15 lines of Java code and 19 lines of html template code (3 of which are significant) has brought us to the point of a fully functional Spring Boot WebMVC app protected by Spring Security and backed by Stormpath.
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 therepackage
execution. You can exercise the same code by just running java:
mvn clean package java -jar target/*.jar
Here’s the Gradle way:
gradle clean build java -jar build/libs/*.jar
Really? Is that it? Yup!
You can browse to http://localhost:8080/ and see it in action.
What if you stored your apiKey.properties
file somewhere else or you have other Stormpath Applications defined? No Problem!
Remember the Application Href you saved from earlier? We are going to use it now.
Here’s the Maven way:
mvn clean package STORMPATH_API_KEY_FILE=~/.stormpath/apiKey.properties \ STORMPATH_APPLICATION_HREF=https://api.stormpath.com/v1/applications/4YHCurbtcaFC4TKKsd3AYQ \ mvn spring-boot:run
Here’s the Gradle way:
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 keys 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 Stormpath Group membership.
Refined Access Control With Spring Security
The code for this section can be found in the BasicAccessControl tag of the code repo.
In the previous section, we created an application that was locked up tight. Every path, including/
, required you to be logged in first.
Perhaps you want a publicly accessible home page. Perhaps you want some parts of the site that any authenticated user can get to and another part of the site where only members belonging to a particular group can get to.
We are going to explore those fine grained controls in this section.
We’ll start by allowing public access to the home page. Users will still have to authenticate to access any another page.
Spring Security: Your Bouncer at the Door
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 up 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.
This is Thymeleaf templates in action. It provides very powerful controls for conditionally showing parts of a template.
Before we make any additional changes, let’s pause here and startup the app as before.
When you browse to http://localhost:8080, you’ll see the unauthenticated version of the home
template.
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.
Let’s set that up to finish up this section.
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. Take note of how the home page changes depending on whether or not you are logged in.
Spring Security Access Control By Group Membership
The code for this section can be found in the GroupAccessControl tag of the code repo.
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
.
Stormpath integrates with this mechanism connecting Stormpath Groups to Spring Security roles.
Let’s look at the code for this and then break it down. We are going to add 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, here. 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 that Spring Expression Language above. Where is Spring Security looking for@roles.ADMIN
? The @
symbol is special – it identifies a Java bean. In this case, a bean namedroles
. 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 calledRoles.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! Let’s dig in.
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 Spring Environment
object to be passed into the constructor. Inside the constructor, we have our only opportunity to set ADMIN
since it’s declaredfinal
– a requirement to be able to use it inside the hasRoles
clause.
The last piece of the puzzle utilizes some Stormpath configuration magic. Notice that we are setting the value of ADMIN
to whatever the environment property namedstormpath.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.
Stormpath adds 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. The Stormpath Java SDK 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, Stormpath will convert the STORMPATH_AUTHORIZED_GROUP_ADMIN
system environment variable to a Spring stormpath.authorized.group.admin
environment variable. That will get picked up by our code above.
Phew! Who thought such magic could be achieved in so few lines of code!
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 theadminService.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:
- Create a new Application
- Create a new Group called “admin” for the Application
- Create a new Account in the admin Group
- Create a new Account NOT in the admin Group
In the code that is associated with 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.
Let’s see it in action. We are going to start up the app as before, only this time we are going to use the Hrefs found in the Admin Console for the new Application and Group you created in the previous step.
Here’s the Maven way:
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
Here’s the Gradle way:
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 as we setup before.
Wrapping It Up
Well, we’ve covered a lot of ground here, haven’t we!
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 the almost magical way 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 lot more features in the Java SDK and the Spring Boot integration. Check out the in-depth tutorial here.
Feel free to drop a line over to support or to me personally anytime.
Building Identity Management, including authentication and authorization? Try Stormpath! Our REST API and robust Java SDK support can eliminate your security risk and can be implemented in minutes. Sign up, and never build auth again!
Awesome guide. Can you provide a screenshot of your project structure, so one can see where all the files are created?
Hi Joe:
I’m the author of the post. It doesn’t look like I can post a screenshot in the comments here, but all of the code used in the post can be found be found here: https://github.com/stormpath/spring-boot-spring-security-tutorial
Best of all, I’ve just update it this past week and it now uses the latest release of Spring Boot, 1.3.6
Best,
Micah