How to use Spring Security to build a simple login page

von

All articles in this series

Intro to Spring Security 5

If you found your way to this page, you probably know what Spring for Java is. Chances are, you built a web application with Spring Web, or even better, Spring Boot, and you now need to authenticate and authorize your users through a login page.

Spring Security 5 was made for just that, and it will get the job done. I’ll be honest, there’s a bit of a learning curve and the documentation is not always helpful, but once you understand the basics, it is quite straightforward to use.

Also, Spring Security 5 will solve most of your problems when it comes to security, and let’s face it: we application developers have lot’s to do and can use any help we can get!

What do you need?

I assume you have at least basic knowledge of Spring and that terms like “Dependency Injection” don’t frighten you. Other than that it’s helpful if you already have a Java Web application with Spring Boot where you can try out the code we’ll be discussing in this article.

Basic terms

Now its really important that you know these terms and remember their meaning as you read through this article.

  • Principal: refers to the authenticated user object provided by Spring Security
  • Authentication: refers to successfully logging in with a username and password
  • Authorization: refers to being allowed to access certain parts of your application
  • Role: refers to a handle to which authorization rules can be assigned

Lets build a simple login form

Spring Security is actually very easy to get started with, if your security requirements are straightforward. You can then build your application to handle scenarios that are much more complex.

Adding Spring Security to your application is easy. Just add the following dependency to your pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

I am assuming you already have the other dependencies like spring-web, spring-core etc. on your classpath. Hopefully, you are using Spring Boot, which is the preferred way to build this type of application.

Also, you’ll need a Controller class which you can access using the browser:

@Controller
public class HomeController {

    @GetMapping("/")
    public String index() {
        return "index";
    }
}

This is a very basic Controller class with a single method mapped to the root of your application.

And of course you’ll need some kind of HTML page as well. Place it at /src/main/resources/templates/index.html:

This is a very basic sample:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Welcome</title>
  </head>
  <body>
    <h1>Welcome</h1>
  </body>
</html>

By the way, I’m using Thymeleaf in this example. You can use any other template engine of your choice, it won’t affect what we’re going to discuss in this article.

One more thing is to tell Spring that your application should be secured. To do so, you need to add the @EnableWebSecurity annotation to your configuration class. This is a class that you should already have for your security configurations.

@EnableWebSecurity
public class SecurityConfiguration {
}

Go ahead, restart your application.

Your application is now already more secure than it was. You’ll be presented with a nice Login page when you try to access your application.

This is the tip of the iceberg we call Spring Security. If you think about it, you’ll see that Spring Security not only protected every page in your application, but it also created a user for you.

You’ll be able to find the password on the command line and you should be able to login now.

Configuring Spring Security: Users

Now, let’s get started on the good stuff.

Let us start by adding two users. Emma and Chris. Apparentely Emma is Chris’s Boss. So she has the “Boss” and “Dev” roles (yes, Emma’s coding skills are very good, that’s why she’s the Boss).

Chris has the role “Dev”. Let’s assume both Chris and Emma use their names as their passwords (don’t do this at home, use a password manager).

To do this, we need to add some configurations. It is pretty nice that Spring Security natively provides us some Adapter classes. Here we look at the WebSecurityConfigurerAdapter. An adapter is a class that implements a specific interface. Now you may not need to implement all methods, so the adapter usually provides useful default implementations.

The WebSecurityConfigurerAdapter implements the WebSecurityConfigurer and has some nice implementations which help us quite a bit.

There is one method I want to override with my own functionality. It’s actually the one which wrote out the password to the command line. As we don’t really want passwords out in the open, we need to give it our own implementation.

The method’s name is “configure” and it takes an AuthenticationManagerBuilder as the only argument - see the code segment below.

@EnableWebSecurity
public class MemorySecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

    }
}

As the name suggests, the AuthenticationManagerBuilder is a class which creates an advanced configuration for authentication. To begin with, let’s start with some in-memory authentication, and use the fluent interface:

@EnableWebSecurity
public class MemorySecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication();
    }
}

Now Spring knows we want to use our RAM to store users. Of course, there are no users yet and we have to add a few ourselves.

Back to Chris and Emma.

@EnableWebSecurity
public class MemorySecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
            .withUser("emma")
                .password("emma")
                .roles("boss", "dev")
            .and().withUser("chris")
                .password("chris")
                .roles("dev");
    }
}

This code should be quite straightforward. If it isn’t don’t worry. All we’ve done here is, told Spring that we want in-memory authentication, with the user “emma”, who uses the password “emma”, and has the roles “boss” and “dev”. We’ve done the same with Chris.

And, on another note, we are storing passwords in plaintext in our code, this is definitely not a good idea. It’s a requirement if you want to be GDPR certified (which you should).

So let’s add some encryption to our passwords. It doesn’t make much sense as we are storing them in our code, but it’s a piece of cake, so let’s just do it anyway.

We are going to use “BCrypt” to encrypt our passwords. Encryption is not in scope for this blog (and I have no clue) so I refer you to competent blogs elsewhere if you want to know more.

Here is the full class, including the BCrypt encryption:

@EnableWebSecurity
public class MemorySecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("emma").password("emma").roles("boss", "dev").and()
                .withUser("chris").password("chris").roles("dev").and()
                .passwordEncoder(new BCryptPasswordEncoder();
    }
}

As you can see, we just create a new instance of BCryptPasswordEncoder and add it to “passwordEncoder”.

Restart your Spring application again, you should be able to login as either Emma or Chris.

We haven’t used any of the roles given to those users. So eventhough We are authenticated, there’s no real authorization happening. To make this happen, another method needs to be overridden.

Again it is called configure, but in the MemorySecurityConfiguration class and it takes another argument: HttpSecurity.

@EnableWebSecurity
public class MemorySecurityConfiguration extends WebSecurityConfigurerAdapter {

    ...

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    }
}

Very similar to the authentication builder, HttpSecurity also has a fluent interface. The first thing to be done is to tell Spring to use the formLogin. This is the default, but let’s make it explicit.

@EnableWebSecurity
public class MemorySecurityConfiguration extends WebSecurityConfigurerAdapter {

    ...

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin();
    }
}

Now let’s add some authorization rules.

@EnableWebSecurity
public class MemorySecurityConfiguration extends WebSecurityConfigurerAdapter {

    ...

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin();

        http.authorizeRequests()
                .antMatchers("/devs/*").hasAnyRole("boss", "dev")
                .antMatchers("/boss/*").hasRole("boss")
                .antMatchers("/").permitAll();
    }
}

First, make a call to authorizeRequests(), as we need access to a “registry” object, which stores the web routes that are allowed for each role. Then “antMatchers” are used to identify the mapping.

All that we have done in this method, is inform Spring that only users with the role “boss” allowed on /boss/_ routes, and that users with either of the roles are allowed on /dev/_ routes. Additionally, have permitted all users on the / route.

Ant matchers are a style taken from the Apache Ant project. Its a little bit like a regular expression, but simpler. More on the options can be found here

Having form-based authentication is alright. But seriously, who stores users in-memory? You should read how to authenticate users through a database next.

Image Credits

Tags: #Java #Spring #Spring Security #Login

Newsletter

ABMELDEN