This guide builds off of Hello Spring MVC Security Java Config to explain how to configure and use a custom login form with Spring Security Java Configuration.

Setting up the sample

This section outlines how to setup a workspace within Spring Tool Suite (STS) so that you can follow along with this guide. The next section outlines generic steps for how to apply Spring Security to your existing application. While you could simply apply the steps to your existing application, we encourage you to follow along with this guide in order to reduce the complexity.

Obtaining the sample project

Extract the Spring Security Distribution to a known location and remember it as SPRING_SECURITY_HOME.

Import the hellomvc sample application

In order to follow along, we encourage you to import the hellomvc sample application into your IDE. You may use any IDE you prefer, but the instructions in this guide will assume you are using Spring Tool Suite (STS).

The completed sample application can be found at SPRING_SECURITY_HOME/samples/javaconfig/form
  • If you do not have STS installed, download STS from https://spring.io/tools

  • Start STS and import the sample application into STS using the following steps:

    • File→Import

    • Existing Maven Projects

    • Click Next >

    • Click Browse…​

    • Navigate to the samples (i.e. SPRING_SECURITY_HOME/samples/javaconfig/hellomvc) and click OK

    • Click Finish

Running the hellomvc application

In the following exercise we will be modifying the spring-security-samples-javaconfig-hellomvc application. Before we make any changes, it is best to verify that the sample works properly. Perform the following steps to ensure that spring-security-samples-javaconfig-hellomvc works.

  • Right click on the spring-security-samples-javaconfig-hellomvc application

  • Select Run As→Run on Server

  • Select the latest tc Server

  • Click Finish

Verify the application is working:

  • A page displaying a user’s inbox can be seen at http://localhost:8080/sample/ after authenticating with the username user and the password password.

  • Try clicking on the Compose link and creating a message. The message details should be displayed.

  • Now click on the Inbox link and see the message listed. You can click on the summary link to see the details displayed again.

Overriding the default configure(HttpSecurity) method

As we saw in Hello Spring MVC Security Java Config, Spring Security’s WebSecurityConfigurerAdapter provides some convenient defaults to get our application up and running quickly. However, our login form does not look like the rest of our application. Let’s see how we can update our configuration to use a custom form.

Default configure(HttpSecurity)

The default configuration for the configure(HttpSecurity) method can be seen below:

protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            .anyRequest().authenticated() (1)
            .and()
        .formLogin()                      (2)
            .and()
        .httpBasic();                     (3)
}

The configuration ensures that:

1 every request requires the user to be authenticated
2 form based authentication is supported
3 HTTP Basic Authentication is supported

Configuring a custom login page

We will want to ensure we compensate for overriding these defaults in our updates. Open up the SecurityConfig and insert the configure method as shown below:

src/main/java/org/springframework/security/samples/config/SecurityConfig.java
// ...

import org.springframework.security.config.annotation.web.builders.HttpSecurity;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login");
    }

    // ...
}

The line loginPage("/login") instructs Spring Security

  • when authentication is required, redirect the browser to /login

  • we are in charge of rendering the login page when /login is requested

  • when authentication attempt fails, redirect the browser to /login?error (since we have not specified otherwise)

  • we are in charge of rendering a failure page when /login?error is requested

  • when we successfully logout, redirect the browser to /login?logout (since we have not specified otherwise)

  • we are in charge of rendering a logout confirmation page when /login?logout is requested

Go ahead and start up the server and try visiting http://localhost:8080/sample/ to see the updates to our configuration. In many browsers you will see an error similar to This webpage has a redirect loop. What is happening?

Granting access to unauthenticated users

The issue is that Spring Security is protecting access to our custom login page. In particular the following is happening:

  • We make a request to our web application

  • Spring Security sees that we are not authenticated

  • We are redirected to /login

  • The browser requests /login

  • Spring Security sees that we are not authenticated

  • We are redirected to /login …​

To fix this we need to instruct Spring Security to allow anyone to access the /login URL. We can easily do this with the following updates:

src/main/java/org/springframework/security/samples/config/SecurityConfig.java
// ...

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll();
    }

    // ...
}

The method formLogin().permitAll() statement instructs Spring Security to allow any access to any URL (i.e. /login and /login?error) associated to formLogin().

Granting access to the formLogin() URLs is not done by default since Spring Security needs to make certain assumptions about what is allowed and what is not. To be secure, it is best to ensure granting access to resources is explicit.

Start up the server and try visiting http://localhost:8080/sample/ to see the updates to our configuration. You should now get a 500 error stating Error resolving template "login".

Creating a login page

Within Spring Web MVC there are two steps to creating our login page:

  • Creating a controller

  • Creating a view

Configuring a login view controller

Within Spring Web MVC, the first step is to ensure that we have a controller that can point to our view. Since our project adds the javaconfig/messages project as a dependency and it contains a view controller for /login we do not need to create a controller within our application. For reference, you can see the configuration below:

// ...

@EnableWebMvc
@ComponentScan("org.springframework.security.samples.mvc")
public class WebMvcConfiguration implements WebMvcConfigurer {

    // ...

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
    }
}

Creating a login view

Our existing configuration means that all we need to do is create a login.html file with the following contents:

src/main/resources/views/login.html
<html xmlns:th="https://www.thymeleaf.org">
  <head th:include="layout :: head(title=~{::title},links=~{})">
    <title>Please Login</title>
  </head>
  <body th:include="layout :: body" th:with="content=~{::content}">
    <div th:fragment="content">
        <form name="f" th:action="@{/login}" method="post">               (1)
            <fieldset>
                <legend>Please Login</legend>
                <div th:if="${param.error}" class="alert alert-error">    (2)
                    Invalid username and password.
                </div>
                <div th:if="${param.logout}" class="alert alert-success"> (3)
                    You have been logged out.
                </div>
                <label for="username">Username</label>
                <input type="text" id="username" name="username"/>        (4)
                <label for="password">Password</label>
                <input type="password" id="password" name="password"/>    (5)
                <div class="form-actions">
                    <button type="submit" class="btn">Log in</button>
                </div>
            </fieldset>
        </form>
    </div>
  </body>
</html>
1 The URL we submit our username and password to is the same URL as our login form (i.e. /login), but a POST instead of a GET.
2 When authentication fails, the browser is redirected to /login?error so we can display an error message by detecting if the parameter error is non-null.
3 When we are successfully logged out, the browser is redirected to /login?logout so we can display an logout success message by detecting if the parameter logout is non-null.
4 The username should be present on the HTTP parameter username
5 The password should be present on the HTTP parameter password
Do not display details about why authentication failed. For example, we do not want to display that the user does not exist as this will tell an attacker that they should try a different username.
We use Thymeleaf to automatically add the CSRF token to our form. If we were not using Thymleaf or Spring MVCs taglib we could also manually add the CSRF token using <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>.

Start up the server and try visiting http://localhost:8080/sample/ to see the updates to our configuration. We now see our login page, but it does not look very pretty. The issue is that we have not granted access to the css files.

Grant access to remaining resources

We need to update our configuration to allow anyone to access our resources and our logout pages. Update the configuration as shown below:

src/main/java/org/springframework/security/samples/config/SecurityConfig.java
// ...

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/resources/**").permitAll() (1)
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()                                    (2)
                .permitAll();
    }

    // ...
}
1 This allows anyone to access a URL that begins with /resources/. Since this is where our css, javascript, and images are stored all our static resources are viewable by anyone.
2 As you might expect, logout().permitAll() allows any user to request logout and view logout success URL.

Restart the server and try visiting http://localhost:8080/sample/ to see the updates to our configuration. We now see a custom login page that looks like the rest of our application.

  • Try entering an invalid username and password. You will see our error message is displayed.

  • Try entering a valid username (user) and password (password). You will be authenticated successfully.

  • Try clicking the Log Out button. You will see our logout success message

Conclusion

You should now know how to add a custom login form using Spring Security’s Java Configuration. To learn more refer to the Spring Security Guides index page.