Join us for Flagship 2024: April 16-17 – Register Now.

Leverage Spring Security to Test in Production

Contents

Spring Security is the “gold standard” in the Java world for integrating authentication and authorization into web applications. It supports standards, like SAML, OpenID Connect, and OAuth. It supports database backends and in-memory storage for user identity data.

Split is the “gold standard” for testing and measuring different experiences in web applications. And, Split enables you to test in production, with a small group of users, without having to redeploy your code. When the test is complete and the new functionality is ready to go live, you flip a switch in Split and – boom – there it is for everyone. Again, without having to redeploy. That is a massive time saver for your development and DevOps teams. (And it’ll make your PM pretty happy too!)

The marriage of Split and Spring Security is so easy that it just might blow your mind!

In this post, I’m going to walk you through building an application that demonstrates how to:

  1. Create a test using Split (called a treatment)
  2. Create an app with Spring Boot and Spring Security that leverages the treatment
  3. Set up the app in such a way that only a “beta test” group has access to new functionality
  4. Update the treatment so that all users get the new functionality
  5. NOT deploy the code more than once

Imagine a web app that’s in production. You want to add some new functionality that’s only available to a “beta test” group of users. When the beta test group gives you the thumbs up that the new functionality is good, you want to enable it for all users. And, you only want to deploy this app once. As a bonus, you want to be able to add new functionality in the future and beta test it in the same way, without having to update the part of the code responsible for managing the test. All of this is possible with Split and is what I am going to show you right now!

Get Started with Spring Boot

The Spring Initializr project makes getting started with Spring Boot a snap. For the example app, I’ll use the webthymeleaf and spring security dependencies. You can customize things like package name and artifact ID. Then, you can download an archive of the complete project scaffolding, ready to be imported into the IDE of your choice.

If you’re a denizen of the terminal like me, you don’t even need the website. You can run the following:

mkdir split-spring-security-beta-test-example && cd split-spring-security-beta-test-example
curl https://start.spring.io/starter.tgz \
-d dependencies=web,thymeleaf,security \
-d groupId=io.split.examples \
-d artifactId=split-spring-security-beta-test-example \
-d packageName=io.split.examples.betatestexample \
-d description="Leverage Spring Security to Beta Test new Features" \
| tar -xvf -
Bash

The important bit is the: dependencies=web,thymeleaf,security which tells Spring Initializr what dependencies to include in the pom.xml file.

NOTE: By default, Spring Boot now uses Java 11. Make sure you have the appropriate JDK installed on your machine. On Mac, I like to use sdkman to manage multiple JDKs.

Import the project into your IDE. I use IntelliJ. You can also find the completed project on GitHub.

At this point, you can actually fire up the project with:

mvn spring-boot:run
Bash

Spring Security has a “deny all unless otherwise specified” approach, which is a security best practice. You’ll notice in the output a default generated password for accessing this bare-bones application.

Something like:

Using generated security password: fe23b3bf-fa77-4c60-9c28-4bf97e1c3300
Bash

If you browse to http://localhost:8080, you’ll see a default login form:

The default username is: user. Using this and the password above, you can login. You’ll immediately see an error on successful login because we haven’t actually created an application yet. But, you can see that authentication is working out of the box.

Set up Some In-Memory Users and Groups

For the purposes of the demonstration, I am going to use a simple, in-memory database of users and groups. In real life, you’d connect Spring Security to an auth service or database for user identities.

I’ll add a SecurityConfig.java file to the project:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    public UserDetailsService userDetailsService() {

        InMemoryUserDetailsManager userManager = new InMemoryUserDetailsManager(
            newUser("linda", "12345678", "USER"),
            newUser("bob", "12345678", "USER"),
            newUser("tina", "12345678", "USER", "BETA_TESTER"),
            newUser("gene", "12345678", "USER", "BETA_TESTER"),
            newUser("louise", "12345678", "USER", "BETA_TESTER")
        );

        return userManager;
    }

    private UserDetails newUser(String username, String password, String... roles) {
        return User
            .withUsername(username)
            .passwordEncoder(
                PasswordEncoderFactories.createDelegatingPasswordEncoder()::encode
            )
            .password(password).roles(roles).build();
    }
}
Java

The private newUser method uses Spring Security’s builder for creating users. It also uses Spring Security’s PasswordEncoderFactories, which defaults to bcrypt hashed passwords, which is a security best practice.

If you restart the application, you can now log in with these five users. Note: you’ll still get an error after login, as we’ve still not set up the application.

Notice that tinagene, and louise all have the BETA_TESTER role. We’ll use this later to ensure that only they see the new functionality, while bob and linda still see the old functionality.

Add Authenticated Pages

We’re using the Thymeleaf templating engine in our project. This makes it easy for us to set up some pages while keeping the application small and simple.

First, we need a controller to direct incoming traffic to our application. Here’s HomeController.java:

@Controller
public class HomeController {

    @GetMapping("/")
    public String home(@AuthenticationPrincipal UserDetails user, Model model) {
        model.addAttribute("username", user.getUsername());
        model.addAttribute("roles", user.getAuthorities());
        return "home";
    }
}
Java

Spring Security automatically passes in the UserDetails object because of the @AuthenticationPrincipal annotation. Spring Web passes in a Model object automatically.

The code adds username and roles to the model and then directs Thymleaf to render the home template by returning it as a String.

Thymeleaf looks for templates by default in src/main/resources/templates. Here’s home.html:

<html xmlns:th="http://www.w3.org/1999/xhtml">
    <head></head>
    <body>
        <h2>
            <span th:inline="text">Hello, [[${username}]]!</span>
        </h2>
        <p/>
        Here are your roles:
        <br/>
        <ul th:each="role : ${roles}">
            <li th:inline="text">[[${role}]]</li>
        </ul>
        <hr/>
        <a href="/logout">Logout</a>
    </body>
</html>
HTML

This template displays the username found in the model on line 5. And, it iterates over the list of roles using th:each on lines 10 – 12.

Notice that there’s a link to /logout at the bottom. Spring Security has default logout logic built in.

Fire up the app again and you should see that you can login as before and see the simple landing page. You should also be able to logout and login as someone else.

Note: You may notice that although we defined the role as BETA_TESTER, it shows up as ROLE_BETA_TESTER. This is due to Spring Security’s ability to support multiple role types, each with its own prefix. ROLE_ underscore is the default.

At this point, the Spring Boot + Spring Security application is fully functional (such as it is).

Next, I talk about Split and then I bring Spring and Split together.

Get Started with Feature Flags

Getting setup with a free developer account for Split is as easy as 1, 2, 3:

  1. Go to: https://www.split.io, click: Free Account
  2. Fill out the registration form and click: SIGN UP
  3. Follow the link you receive in email and set a password.

Create the Treatment in Split

Treatments allow you to define settings and behaviors for what you want to test. For my example, we want to setup a treatment that will return on or off depending on whether or not you are part of the beta tester group.

To start, click DE in the upper left. Choose Admin Settings and API Keys. Copy the value for sdk Type in the prod-default Environment. You’ll need this in the Spring Boot app shortly.

Next, Click Splits on the left-hand side and click Create Split. Give it a Name. Leave the other defaults and click Create.

Next, click Add Rules on the Targeting Rules tab. Split automatically adds on and off treatment definitions and sets off as the default.

For the use-case in this example, we want to add a group for which the treatment will return a value of on.

Click Add Rule in the Set Targeting Rules section. Here, we want to have the treatment return “on” if the user is in the group of beta testers. To accomplish this, enter groups in the Add attribute field. From the Select matcher dropdown, choose Set > has any of and enter ROLE_BETA_TESTER in the field. Change the serve dropdown to on.

This now makes it read like an english sentence: “If the user has an attribute called groups and the groups list contains the value ROLE_BETA_TESTER, then serve ‘on’ for the treatment”

Click Save Changes

Click Confirm on the summary screen.

Integrate the Treatment with Spring Security

Edit the pom.xml file in the project. Add the following dependency:

<dependency>
    <groupId>io.split.client</groupId>
    <artifactId>java-client</artifactId>
    <version>4.0.1</version>
</dependency>
XML

This brings the Split Java SDK into scope for the project.

Next, add a configuration to the project to make the Split Java Client available to the application. Here’s SplitConfig.java:

@Configuration
public class SplitConfig {

    @Value("#{ @environment['split.api-key'] }")
    private String splitApiKey;

    @Bean
    public SplitClient splitClient() throws Exception {
        SplitClientConfig config = SplitClientConfig.builder()
                .setBlockUntilReadyTimeout(1000)
                .enableDebug()
                .build();

        SplitFactory splitFactory = SplitFactoryBuilder.build(splitApiKey, config);
        SplitClient client = splitFactory.client();
        client.blockUntilReady();

        return client;
    }
}
Java

Create a file under src/main/resources called application.yml and place the following in it:

split:
  api-key: <your Split API Key>
YAML

This is the key line that sets up the Split Client for use elsewhere in the code:

SplitFactory splitFactory = SplitFactoryBuilder.build(splitApiKey, config);
Java

Let’s set up a new template called home-beta.html (It’s mostly copypasta from the original template and should be located in: src/main/resources/templates):

window.NREUM||(NREUM={});NREUM.info={"beacon":"bam.nr-data.net","licenseKey":"NRJS-ebe8ce2e5a2e9c05baf","applicationID":"1126849900","transactionName":"NFJTYxNRDRcDVkFeWg0YcFQVWQwKTUZcWVIPUg==","queueTime":0,"applicationTime":1955,"atts":"GBVQFVtLHhk=","errorBeacon":"bam.nr-data.net","agent":""} ” style=”color:#D4D4D4;display:none” aria-label=”Copy” class=”code-block-pro-copy-button”>
<html xmlns:th="http://www.w3.org/1999/xhtml">
    <head></head>
    <body>
        <h1>WELCOME TO THE BETA EXPERIENCE</h1>
        <h2>
            <span th:inline="text">Hello, [[${username}]]!</span>
        </h2>
        <p/>
        Here are your roles:
        <br/>
        <ul th:each="role : ${roles}">
            <li th:inline="text">[[${role}]]</li>
        </ul>
        <hr/>
        <a href="/logout">Logout</a>
    </body>
</html>
HTML

The last piece of the puzzle is in our HomeController. I want to have the app render the new home-beta template if the authenticated user is in the ROLE_BETA_TESTER group. Here’s the updated controller:

@Controller
public class HomeController {

    SplitClient splitClient;

    public HomeController(SplitClient splitClient) {
        this.splitClient = splitClient;
    }

    @GetMapping("/")
    public String home(@AuthenticationPrincipal UserDetails user, Model model) {
        model.addAttribute("username", user.getUsername());
        model.addAttribute("roles", user.getAuthorities());

        List<String> groups =  user.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority).collect(Collectors.toList());
        String inBeta = splitClient.getTreatment(
            user.getUsername(),
            "BETA_UI_EXPERIENCE",
            Map.of("groups", groups)
        );

        return "on".equals(inBeta) ? "home-beta" : "home";
    }
}
Java

The first thing to notice is that I am injecting the Split Client using the constructor dependency injection of Spring.

The real magic happens with the splitClient.getTreatment call. The first parameter is the username provided by Spring Security for the authenticated user.

Note: In much of the documentation, this parameter is referred to as a key. Don’t confuse this with the API Key, which should NEVER be used as the first parameter to the getTreatment call.

The second parameter is the name of the treatment in Split that we want to target.

The third parameter sends a map where the key is groups and the value is the list of group names that the authenticated user belongs to. In the case of our user linda, this will be ROLE_USER. In the case of our user louise, this will be ROLE_USER and ROLE_BETA_TESTER.

The last line of the controller method now returns the home-beta template if the result of getTreatment is on and the home template otherwise.

Fire up the app and try to login as linda. You should see the same home template as before. Now, logout and login as louise. You should see the new beta template.

Make Your New Functionality Generally Available

Now that we have different treatments of our home template for regular users and beta testers, you may be wondering how to go about making the beta template available to everyone?

Split makes it easy-peasy. Go back to your Split definition for Spring_Security_App and switch the serve setting in the Set The Default Rule section from off to on. Save and Confirm the change.

Without restarting the Spring Boot application, logout and login as linda again. You should now see the same page that you saw for louise earlier.

Pretty cool, eh?

How to Repeat the Beta / Release Cycle

With this architecture in place, it’s now very easy to set up a new beta cycle. The steps would be something like this:

  1. Copy the home-beta.html template to home.html (now that it’s ready for production)
  2. Create a new home-beta.html template
  3. Set the default rule back to off in Split
  4. Redeploy the app
  5. Let your beta testers test the new experience
  6. When ready, set the default rule back to on in Split
  7. NO NEED TO REDEPLOY

You could do this over and over again and never touch the controller code. The only thing that’s changing are the templates and the settings in split.

This approach also lends itself to changing who is in your beta test program without having to change your code.

In a real application, you’d be working with a database or an Identity Management system where you could add and remove users to the BETA_TESTERS group. Those users would always see the latest and greatest beta while ordinary users would see only the current release.

I hope you’ve seen how useful it can be to set up different experiences for different users using Split and the native functionality built into Spring Security.

Learn More About Spring, Spring Security, and Testing in Production

If all this post did was wet your whistle, I’ve got good news… between the security experts at Okta and the testing aficionados at Split, we have you covered. Here are some other posts to check out:

You can find me talking about Java and security topics on Twitter @afitnerd and blogging on Okta’s developer blog. You can follow Split on YouTubeTwitter, and LinkedIn for more on progressive delivery, experimentation, and testing in production.

Get Split Certified

Split Arcade includes product explainer videos, clickable product tutorials, manipulatable code examples, and interactive challenges.

Deliver Features That Matter, Faster. And Exhale.

Split is a feature management platform that attributes insightful data to everything you release. Whether your team is looking to test in production, perform gradual rollouts, or experiment with new features–Split ensures your efforts are safe, visible, and highly impactful. What a Release. Get going with a free accountschedule a demo to learn more, or contact us for further questions and support.

Want to Dive Deeper?

We have a lot to explore that can help you understand feature flags. Learn more about benefits, use cases, and real world applications that you can try.

Create Impact With Everything You Build

We’re excited to accompany you on your journey as you build faster, release safer, and launch impactful products.