Secure Your Java App with Spring Security, Thymeleaf, and Okta
Never Build Auth Again – Love building user management? With Okta, you can add social login, multi-factor authentication, and OpenID Connect support to your app in minutes. Create a free developer account today.
When you’re building your java app, user management is a critical consideration. It’s common for apps and APIs to partition access to different parts of an application, depending on the roles assigned to a user – this is role-based access control (RBAC).
This is where Okta comes in – Okta simplifies this by managing roles within groups (users can belong to one or more groups). With Okta’s Spring Security integration, this process becomes automated through the use of common annotations that map groups to specific roles and either allow or deny access. This is done using common Spring Security annotations, which we’ve outlined below.
To show this in practice, I’ve put together a demo below that walks through a simple, but common scenario. For our example, we’ll look at a mix of unprotected pages, pages that only an authenticated user can access, and pages that require users to have an extra level of authorization before they can access them.
We’ve added all of the code we reference throughout here.
If you’re already looking to dive in and get started with built-in RBAC for your Java app, simply wire up your Okta tenant to a Spring Boot application here.
If you have any questions, contact Okta’s dev support team here.
Step One: Configure Your Okta Account
The first step is setting up your Okta tenant. This way, you can launch the sample app we’ve created and see this in action. Sign up for a developer account here and then follow the steps below:
- Create an
admins
group - Create a
users
group - Create a user who belongs to the
users
groups - Create a second user who belongs to both groups
- Create an OpenID Connect (OIDC) application
- Add the two groups to the application
- Configure the default Authorization Server to include group memberships in access tokens
Let’s walk through what this looks like:
Setting Up Groups
In your Okta Admin Dashboard menu, find Users
and then click Groups
. Click Add Group
and enter admins
in the Name
field and add a group description, like: Admins. Click Add Group
to finish this step.
Follow the same process to add a users
Group.
Setting Up Users
Again, navigate to Users
in the Okta Admin Dashboard, but this time click People
. Click Add User
and fill in the form with the user information. (Use a real email address for either the primary or secondary email address – and one that you have access to – so that you can validate the email later.) In the Groups
field, add this users to the users
group you created earlier. Make sure you’ve clicked the Send user activation email now
checkbox and then click “Save and Add Another”.
Repeat the steps we outlined above, only this time, add the second user to both the users
and admins
groups.
Hop over to your email and validate those email addresses. Click the links for both users to activate them.
Creating an OIDC Application
Now it’s time to set up your authentication layer.
In the Okta Admin Dashboard, click Applications
in the menu and then click Add Application
.
Choose Web
and click Next
.
When you’re prompted to fill out the form, use these values:
| Field | Value | | ------------------- | ------------------------------ | | Name | Fun with Spring Security Roles | | Base URIs | http://localhost:8080/ | | Login redirect URIs | http://localhost:8080/ | | Group assignments | `admins` and `users` | | Grant type allowed | Check: `Implicit` |
Once you’re ready, click `Done` and you’ll see the resulting page below:
The URIs specified are the Spring Boot defaults. You can easily change these later on.
Before moving on to the next step, scroll down and note the Client ID
. You’ll need it to configure the Spring Boot app.
Setting Up Your Authorization Server
Next, look for API
in the Okta Admin Dashboard menu and click Authorization Servers
to launch the following:
Take note of the Issuer URI
. You’ll also need this later to configure the Spring Boot app.
Click default
and choose the Claims
tab. Click Add Claim
and fill out the fields as follows:
| Field | Value | | --------------------- | ------------ | | Name | groups | | Include in token type | Access Token | | Value type | Groups | | Filter | Regex .* | | Include in | Any scope |
Click Create
to finish this step.
Creating this Claim ensures that group membership information is included in the Access Token when a user authenticates. This step is critical to hook into Spring Security’s roles and authorities mechanism.
Step Two: Configure Your Spring Boot App
First, clone the at this link.
Open the project in your preferred IDE or editor. The screenshots below are from here.
Make a copy of the application.yml.sample
file and name it application.yml
Remember those values we flagged to save earlier? Update your information with those. Here’s our example:
| Name | Value | | ----------- | ------------------------------------------------- | | baseUrl | https://dev-237330.oktapreview.com | | issuer | https://dev-237330.oktapreview.com/oauth2/default | | audience | api://default | | clientId | 0oacdldhkydGGruON0h7 | | rolesClaim | groups | | redirectUri | http://localhost:8080/ |
Before we jump into the code, let’s see the application in action.
Run this from the command line to get started:
mvn spring-boot:run
See the App in Action
Navigate to the home page and click Login
.
To sign in, use the credentials for the user that belongs to the Users
group you created in step one. From there, you’ll see that the app shows your user information and displays a row of buttons below it.
These correspond with access permissions within the app. Members of the users
group will be able to see the page when Users Only
is clicked. This is the same case for users of the admins
group, who can see the page when Admins Only
is clicked.
Let’s see how this works.
Click Users Only
. You’ll see a page that shows that you’re a member of the users
group.
Click Back
and then click Admins Only
. This time, you’ll get a 403 Unauthorized
page because you’re not a member of the admins
group.
Click Logout
and sign in again, but this time as the user that belongs to both groups (the second user you created in Step One).
Click Admins Only
.
This time, you’ll see that you are in both the admins
and users
groups.
Pretty straightforward! Now let’s jump into the code…
Step Three: Spring Security Code Review
This section outlines how Okta groups link up to Spring Security roles.
The demo application uses the following:
- Spring Boot
- Spring Security
- Spring Security OAuth2
- Okta Spring Security Starter
- Thymeleaf Templates
- Thymeleaf Extras for Spring Security 4
- Okta Sign-In Widget
We depend on the okta-spring-security-starter
(from pom.xml). That’s how the behind-the-scenes magic happens:
... <dependency> <groupId>com.okta.spring</groupId> <artifactId>okta-spring-security-starter</artifactId> <version>0.1.0</version> </dependency> ...
Let’s start with the Javascript Okta Sign-In Widget and see how it connects the client side to Spring Boot.
Setting Up the Okta Sign-In Widget
Here’s how we set up the Okta Sign-In widget in the `login.html` Thymeleaf template:
$( document ).ready(function() { var data = { baseUrl: [[${appProperties.baseUrl}]], clientId: [[${appProperties.clientId}]], redirectUri: [[${appProperties.redirectUri}]], authParams: { issuer: [[${appProperties.issuer}]], responseType: ['token'] } }; window.oktaSignIn = new OktaSignIn(data); // Check if we already have an access token var token = oktaSignIn.tokenManager.get('token'); if (token) { window.location.href = "/authenticated"; } else { renderWidget(); } });
In lines 3–8, you’ll see that we’ve embedded all the settings to connect to our Okta tenant as inline Thymeleaf Template variables. These values are passed in from the Spring Boot controller as part of the model. By doing this, you only have to specify these settings once – now both the server and client side can make sure of them. We’ll elaborate on how these settings are managed below (they all come from the application.yml
file).
After we’ve configured and instantiated the Okta Sign-In Widget, the next step is to check whether or not the user has logged in. Then we take one of two actions. If they have, we send them to the /authenticated
page. If they haven’t, we render the widget, which gives the user the opportunity to log in.
Here’s the renderWidget
function:
function renderWidget() { oktaSignIn.renderEl( {el: '#okta-login-container'}, function (response) { // check if success if (response.status === 'SUCCESS') { // for our example we have the id token and the access token oktaSignIn.tokenManager.add('token', response[0]); if (!document.location.protocol.startsWith('https')) { console.log( 'WARNING: You are about to pass a bearer token in a cookie over an insecure\n' + 'connection. This should *NEVER* be done in a production environment per\n' + 'https://tools.ietf.org/html/rfc6750' ); } document.cookie = 'access_token=' + oktaSignIn.tokenManager.get('token').accessToken; document.location.href = "/authenticated"; } }, function (err) { // handle any errors console.log(err); } ); }
Once the widget is rendered on the page, the internal logic takes over based on your settings when the user logs in. In this case, you are using this flow and will get back only an access token as specified by the responseType
parameter of the configuration.
On successful login, you enter the callback function with a response
object. The response object has your (or in this case, your user’s) access token.
Line 19 sets a cookie with the access token and line 20 sends the (now authenticated) user to the /authenticated
endpoint.
At this point, Spring Security can recognize the authenticated user.
Before we look at Spring Security roles, let’s see how Spring Security deals with the access token.
Spring Security Token Extractor
By default, the Spring Security OAuth 2.0 plugin processes access tokens coming in on an Authorization
header as a bearer token. This is fine for applications that are creating RESTful responses for clients, such as an Angular client.
For this example, I kept the architecture and amount of Javascript minimal, so I wanted full page transitions. This is a little old-school, but it keeps the example code tight and small.
In order for Spring Security to recognize that a user has authenticated, we need it to be able to handle the token coming in on a cookie.
Fortunately, Spring Security makes it pretty easy to override the default behavior by setting a TokenExtractor
. Here’s the code that makes this happen from OktaSpringSecurityRolesExampleApplication
:
@Bean protected ResourceServerConfigurerAdapter resourceServerConfigurerAdapter() { return new ResourceServerConfigurerAdapter() { ... @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.tokenExtractor(new TokenExtractor() { @Override public Authentication extract(HttpServletRequest request) { String tokenValue = findCookie(ACCESS_TOKEN_COOKIE_NAME, request.getCookies()); if (tokenValue == null) { return null; } return new PreAuthenticatedAuthenticationToken(tokenValue, ""); } ... }); } }; }
What this does is pull the access token from the list of cookies on the incoming request, if it can. Then the parsing and validation is done automatically. Voila!
Establishing Role Based Access Controls
In the application setup, you define which paths are open. All other paths require an authenticated session at least.
Here’s another excerpt from OktaSpringSecurityRolesExampleApplication
:
@Bean protected ResourceServerConfigurerAdapter resourceServerConfigurerAdapter() { return new ResourceServerConfigurerAdapter() { @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/login", "/images/**").permitAll() .and() .exceptionHandling().accessDeniedHandler(customAccessDeniedHandler); } ... }; }
In this case, you are telling Spring Security to let any unauthenticated user access the home page (/
), the login page (/login
), and anything that comes from the static images folder. This means that every other path is automatically restricted by default.
At this point, you also define a custom access-denied handler.
@Controller public class SecureController { @Autowired protected AppProperties appProperties; @RequestMapping("/authenticated") public String authenticated(Model model) { model.addAttribute("appProperties", appProperties); return "authenticated"; } @RequestMapping("/users") @PreAuthorize("hasAuthority('users')") public String users() { return "roles"; } @RequestMapping("/admins") @PreAuthorize("hasAuthority('admins')") public String admins() { return "roles"; } @RequestMapping("/403") public String error403() { return "403"; } }
Within this controller, we’ve defined four paths, which all require an authenticated user at a minimum.
The real value comes in with the /users
and /admins
paths. Notice that they both have the @PreAuthorize
annotation. This means that the expression that follows must be satisfied before the method will even be entered. The hasAuthority
function confirms whether the authenticated user belongs to those roles. In this example, these are automatically mapped to the Okta groups we created previously, which is why it was important to include the groups
claim in the access token from Okta.
While this involves a bit of set-up, now you have role-based access control with just one line of code!
End-to-End Configuration
On the client side, we have a set of HTML pages in the form of Thymeleaf templates served from the application itself. Because of this, it makes sense to have a single source for configuration values required by both the client and server.
This is easy with Spring’s @Component
and @ConfigurationProperties
annotations.
Here’s the AppProperties
class:
@Component @ConfigurationProperties("okta.oauth") public class AppProperties { private String issuer; private String audience; private String clientId; private String rolesClaim; private String baseUrl; private String redirectUri; ... getters and setters ... }
The @ConfigurationProperties
tells Spring to pull in all the properties from the application.yml
file that belong to the okta.oauth
key.
The @Component
annotation causes Spring to instantiate this Object and make it available for auto-wiring elsewhere.
Take a look at this snippet from the HomeController
:
@Controller public class HomeController { @Autowired protected AppProperties appProperties; ... @RequestMapping("/login") public String login(Model model) { model.addAttribute("appProperties", appProperties); return "login"; } }
Before returning the login
view when the /login
endpoint is hit, the AppProperties
object (autowired in on lines 4 & 5) is added to the model.
This is what makes it available to the Thymeleaf template as you saw before:
<script th:inline="javascript"> /*<![CDATA[*/ $( document ).ready(function() { var data = { baseUrl: [[${appProperties.baseUrl}]], clientId: [[${appProperties.clientId}]], redirectUri: [[${appProperties.redirectUri}]], authParams: { issuer: [[${appProperties.issuer}]], responseType: ['token'] } }; window.oktaSignIn = new OktaSignIn(data); ... }); ... /*]]>*/ </script>
Start Coding!
And that’s it! Give it a go and let me know how it goes! I’m on Twitter.
While I’ve outlined the benefits of using Okta’s Groups mechanism with Spring Security’s role-based access control, Okta’s Java dev team is working hard on our next generation SDK and integrations as well. Keep an eye out for upcoming releases of the new Okta Java Spring Boot Integration, which will have have support for other OIDC workflows, including code
as well as hosted, configurable login and registration views.
This post has been adapted from here.
Never Build Auth Again – Love building user management? With Okta, you can add social login, multi-factor authentication, and OpenID Connect support to your app in minutes. Create a free developer account today.