This post looks at a standard form-based authentication and how it applies to Spring WebFlux. Follow along a complete working example and tweak the source code to your own liking.

Project Setup

The stack:

  • Spring Boot
  • Spring Security
  • Kotlin + Gradle
  • Java 11

Spring Boot allows you to easily build upon established libraries and provides production grade integration points. It saves a tone of time.

Spring Security is the primary framework will be focusing on throughout this series. In a nutshell, it lets you configure security rules in your application and provides guarantees that the protection will adhere to industry standards.

Kotlin. I prefer Kotlin to Java mostly because it is less verbose, evolves quickly, learns from the design mistakes made in Java and is backed up by a reputable company.

Gradle. Unlike Maven, Gradle is more of a scripting language. Kotlin DSL even brings compile time safety. The advantage is greater flexibility. You can tailor your build process in almost any thinkable way. The drawback on the other hand is that things can get tricky at times. In this tutorial we won’t be doing anything advanced with Gradle. Feel free to stick to Maven, if that’s what you are more comfortable with.

Java 11. Nothing much to add here. Java 11 enjoys a long-term support. It has been recently superseded by another LTS release, Java 17, but it will take a while before the new version of Java gains a significant adoption.

Here is how to generate the skeleton using Spring Initializr.

Today’s Takeaway

  • Apply Spring Security in the context of a reactive web application
  • Access user details and present them on a user profile page
  • Understand the authentication process

A complete example is available on GitLab.

Follow the video tutorial below for a step-by-step guidance.

First Steps

Spring Security requires virtually no configuration. Add this dependency:

implementation("org.springframework.boot:spring-boot-starter-security")

.. and out of the box all your web routes are protected by a login form. The user repository is pre-populated with a single user called user with a random password printed to the console on INFO level at bootstrap time.

Making Adjustments

The default config will hardly suffice even for a hobby project, let’s make a few adjustments.

Step 1: Gain control over the authentication setup

Why to rely on the default wiring? What if in the next version of Spring they opt for a basic authentication instead of a form-based? What if they stop providing that good looking login form?

Take an active approach and redefine the configuration from scratch. It only takes a few lines of code.

Create a new class, I like to call it SecurityConfig and annotate it with @EnableWebFluxSecurity.

Next, define the security filter chain. The example below reflects what’s provided to you by default. That is, no anonymous access is allowed, all routes are behind a login form.

@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity):   SecurityWebFilterChain =
        http
            .authorizeExchange()
            .anyExchange().authenticated()
            .and().formLogin()
            .and().build()

Step 2: Know your users!

The default test user gets you started, but you most likely want to enable access to your own user base. If you are still experimenting or getting your feet wet with Spring Security you can stick to an in-memory database. It’s easy to use and allows for quick prototyping until you figure out all the details.

Register your custom user service as follows.

@Bean
fun userDetailsService(): MapReactiveUserDetailsService {
   val user: UserDetails = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build()
   return MapReactiveUserDetailsService(user)
}

You can add as many users as you want. Yes, it’s all hard-coded and the user details are very limited. That’s fine for now. I will show you how to add more information in the next post.

Step 3: Create a user profile page

While Spring ships with a login form, it does not provide the default landing page. The moment you successfully log in using the random password generated when the app starts up, you hit 404 since there is no content to show. Let’s fix that.

In order to display user details you need to be able to access the object representing the authenticated user. Thymeleaf Extras make this super easy. First of all, add a dependency:

implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity5")

Next, create a new html page index.html under src/main/resources/templates directory.

<html lang="en" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
...
<p>Hello, welcome back! You are logged in as <span sec:authentication="principal.username"></span></p>

Now, when I log in using the generated password, I will get to see user’s details on the landing page.

Step 4: Replace the generated password with your own

The generated password really has no use. Let’s replace it with a static one for now. Later on, I will show you how to load users from a database, but that’s a topic for another time.

To add a new user, we need to add a custom user details service. For now, it will return a single user with hardcoded credentials.

@Bean
fun userDetailsService(): ReactiveUserDetailsService {
    val user: UserDetails = User.withDefaultPasswordEncoder()
        .username("user")
        .password("password")
        .roles("USER")
        .build()
    return MapReactiveUserDetailsService(user)
}

That’s all. Once you restart the app you will be able to log in with the hardcoded credentials.

Conclusion

In this post I showed you how to apply minimum changes to the default configuration, so that you gain a complete control over the authentication steps.

Fork the complete example on GitLab and make it your own.

Watch this episode on YouTube, give it a like, subscribe to the channel and stay tuned for my next episode where I explain how to load users from a database.


Tomas Zezula

Hello! I'm a technology enthusiast with a knack for solving problems and a passion for making complex concepts accessible. My journey spans across software development, project management, and technical writing. I specialise in transforming rough sketches of ideas to fully launched products, all the while breaking down complex processes into understandable language. I believe a well-designed software development process is key to driving business growth. My focus as a leader and technical writer aims to bridge the tech-business divide, ensuring that intricate concepts are available and understandable to all. As a consultant, I'm eager to bring my versatile skills and extensive experience to help businesses navigate their software integration needs. Whether you're seeking bespoke software solutions, well-coordinated product launches, or easily digestible tech content, I'm here to make it happen. Ready to turn your vision into reality? Let's connect and explore the possibilities together.