Beginning Spring Security authentication on Spring Boot

Although the Spring suite of projects is usually easy to integrate, you might have noticed that you usually end up typing the same configuration again and again, with only a few (but important!) details changing from project to project. Teams usually end up setting a “template” configuration which they clone and adapt for every new application. Isn’t there a better way to start a Spring project?

Well yes Sir: there’s Spring Boot!

Off with the configuration chore!

The aim of Spring Boot is to get you started as easily as possible. It proposes to setup and auto-configure your Spring-based project based on specified starter POMs and sensible default settings.

For example, have a look at this POM:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
<modelVersion>4.0.0</modelVersion>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>MyProject</name>
    <groupId>com.mycompany</groupId>
    <artifactId>myproject</artifactId>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.1.6.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>
</project>

The above loads Spring Boot as the parent, then uses the four “starter” dependencies to:

  • add support for web development, including Spring MVC
  • support logging with the LogBack framework
  • import Jetty as the engine to run your application as standalone
  • add support for Spring Security

How easy was that?

Want to add support for Velocity or JPA? Just add the correct starter POM to the dependencies. Want to start writing those unit tests? Add the “test” starter POM and get JUnit, Hamcrest, Mockito and the Spring Test module all  at once.

Setting up an initial, vanilla configuration for your project is as easy as:

@ComponentScan
@EnableAutoConfiguration
public class MyApp {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(MyApp.class);
        app.run(args);
    }
}

Notice the @EnableAutoConfiguration annotation? That tells SpringBoot to try and guess the most sensible configuration based on the specified dependencies.

Agreed, that won’t take you very far. But it’s a good starting point. From here you can start overriding Spring Boot defaults in order to customize the configuration based on what you need.

There would be a lot to write about Spring Boot, and to be honest I probably just started to scratch the surface of it. I kindly invite you to check spring.io for tutorials and examples on this. Mind you, their manual is also very well written!

Enabling Spring Security on Spring Boot

In this article I want to focus on some basic Spring Security configuration when working on top of Spring Boot.

As with every other feature, spring security is added by including the matching starter POM. Just by including that POM will get you a basic configuration setup that includes HTTP Basic authentication for all material except common static resources (css, js, etc.), low-level features such as XSS and CSRF protection, and an AuthenticationManager bean with an in-memory default user created for you.

When you run your Spring Boot application as a Java Application from your IDE, you will then notice the generated default user’s password in the logs:

Using default security password: 8a20d976-f937-49d3-a55f-059a1f6964ea

This is a great initial setup when you are at the very first development phases: you can now log in using “user” as the username and… that thing up there as the password.

Overriding the security defaults

Of course you might end up getting annoyed with this: every time you restart the application and need to log in, you need to search the generated password and copy-paste it to authenticate. Hardly practical!

Hopefully Spring Boot allows you to easily override this password generation by specifying these properties in an application.properties file located at the root of your resources (that is, src/main/resources):

security.user.name=myOwnUser
security.user.password=myOwnPassword
security.user.role=ADMIN

Restart the application and you can now log in using those credentials. That’s better!

… Although at some point you might end up needing more than one user for testing purposes. In order to do that we will need to bring in some Java configuration.

Authentication customization

In order to register more than one user, we need to build our own AuthenticationManager configuration. The easiest way is to extend an instance of a WebSecurityConfigurerAdapter and override whatever we need, then expose that adapter as a bean.

For example, to build our own AuthenticationManager we can continue implementing the MyApp class like this:

@ComponentScan
@EnableAutoConfiguration
public class MyApp {

    @Bean
    public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter() {
        return new MySecurityConfigurer();
    }

    @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
    public static class MySecurityConfigurer extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(AuthenticationManagerBuilder builder) throws Exception {
            builder.inMemoryAuthentication()
              .withUser("user").password("user").roles("USER")
              .and().withUser("admin").password("admin").roles("ADMIN");
        }
    }
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(MyApp.class);
        app.run(args);
    }
}

The overridden method in the the static class exposes a builder instance which allows you to configure your users in a very straightforward manner. We use an in-memory authentication in our example, for simplicity reasons. But rest assured that you are not limited to in-memory storage for your application’s users!

When running the application again you will be able to log in using your configured users. However you will notice that the HTTP Basic authentication is gone: Spring Security now presents you a basic form to enter your credentials.

HTTP security customization

Let’s take back control of our HTTP security. In order to restore the HTTP Basic authentication, we override another configuration() method from the extended WebSecurityConfigurerAdapter:

@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public static class MySecurityConfigurer extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder builder) throws Exception {
        builder.inMemoryAuthentication()
          .withUser("user").password("user").roles("USER")
          .and().withUser("admin").password("admin").roles("ADMIN");
    }
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
    }
}

This should give us back our beloved HTTP Basic authentication.

Of course, you might probably prefer a custom implementation of a login page. This can also be done using the HttpSecurity instance exposed by that configure() method:

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

The above code states that a form-based login will be enforced when authentication is needed. It will use the specified login page, which must be accessible (permitted) to all (otherwise no one will be able to access that login page, which might be a bummer!).

So how about the rest of the chained methods?

Well, there’s a good chance that you will want your login page to be nice to look at. In order to do that, you will need for some resources to be accessible no matter what. Spring Boot makes some static resources accessible by default through Ant matchers: /css/**, /js/**, /images/** and **/favicon.ico will be permitted by default.

In the example above though, we also need access to a fonts folder which contains glyphicons used by the login page. So we specify that the default static resources, plus the content of the fonts folder specified by the ant matcher, should be permitted to all, while any other request to a resource should require authentication.

You can take it from here…

From this point on you should be able to take things into your hand by leveraging your Spring Security knowledge.

Usually the tricky part when working on top of Spring Boot is to understand what the framework provides by default and know how to override that, so that you can start using your configuration. I remember fighting Spring Boot in order to configure persistence with JPA, until I finally understood that it was all already configured for me.

Same thing goes with security: try to just override what needs to be, and let Spring Boot guide you for the rest.

Or if you really want to switch off the defaults entirely, you can add that @EnableWebSecurity annotation to your java configuration and take it from there. You’re the boss.

Cheers!

6 comments

  1. manish Reply
    02/10/2015 at 18:57

    Overriding the security defaults

    security.user.name=myOwnUser
    security.user.password=myOwnPassword
    security.user.role=ADMIN

    does not work it says that unauthorized — Bad Credentials

    • codesandnotes Reply
      02/10/2015 at 20:38

      No it couldn’t work: the override of that configure() method takes over the application.properties. If you want to specify the username/password in the properties’ file, you will need to remove this bit of code:

      @Override
      protected void configure(AuthenticationManagerBuilder builder) throws Exception {
          builder.inMemoryAuthentication()
            .withUser("user").password("user").roles("USER")
            .and().withUser("admin").password("admin").roles("ADMIN");
      }
      
  2. codeSec Reply
    07/01/2016 at 09:11

    Thanks for your post. It’s quite interesting!
    What kind of modifications I should do in case of mutual authentication (with client certificate)?

    Thanks!!

    • codesandnotes Reply
      07/01/2016 at 10:29

      Hello codeSec!

      Your question on mutual (ssl) authentication is very interesting, but maybe a bit out of scope for this post which examines the case of authenticating against an “open”, public app/service on the web. In that context, asking the user to generate his certificate is a bit of an overkill IMHO

      I honestly don’t have enough knowledge to satisfyingly answer your question! At best I can redirect you to this Stack Overflow question which regroups an extensive list of resources on the subject.

      It’s no small feat, so if you feel like blogging about this when you manage to pull it off I’ll be your first reader 😉
      Cheers!

  3. ddemott Reply
    15/11/2017 at 12:07

    Good post. You do have some HTML that isn’t rendering in your code (in Chrome). The elements are showing up in your code rather than rendering.

    • codesandnotes Reply
      15/11/2017 at 13:16

      Hi there,
      Thanks for pointing that out!
      I’ve cleaned that up now.
      Cheers!

Leave A Comment

Please be polite. We appreciate that. Your email address will not be published and required fields are marked

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

%d bloggers like this: