Enterprise Java

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:

  1. Create an admins group
  2. Create a users group
  3. Create a user who belongs to the users groups
  4. Create a second user who belongs to both groups
  5. Create an OpenID Connect (OIDC) application
  6. Add the two groups to the application
  7. 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:

Tip
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:

  1. Spring Boot
  2. Spring Security
  3. Spring Security OAuth2
  4. Okta Spring Security Starter
  5. Thymeleaf Templates
  6. Thymeleaf Extras for Spring Security 4
  7. 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.

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button