Secure REST Services in Spring Boot

Turning on security in a Spring Boot web app is very easy. You simply add an annotation and you automatically get a whole bunch of things including Basic Authentication. Similarly, it is easy to create secure REST services in Spring Boot. To customize the authentication for these services I had to search quite a bit since it seemed that there were many ways to skin the cat. This post summarizes my findings.

To start off, I will assume maven is being used and the pom includes at least the following:


<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>1.5.2.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-security</artifactId>
   </dependency>
</dependencies>

Here are the experiments that I went through to understand the effect of each change in the Spring Java configuration.

1. Basic Auth: Adding

@EnableWebSecurity

Just adding this annotation on any configuration class in your app enables Basic Authentication with the user name “user” and an auto-generated password that gets printed to the console when you run your app. Just look in the console for a line that looks like this:

 Using default security password: d735f27d-cccf-40a1-b35f-6c51552a27e6

A generated random password that changes on every restart is not very useful! So on to the next step.

2. In-memory user database: To control user names and passwords you can configure authentication by calling methods on the AuthenticationManagerBuilder. Just declare a public method on any @Configuration class and annotate it with @Autowired and configure the builder as follows:

@Configuration
@EnableWebSecurity
public class MySecurityConfig {

    @Autowired
    public void anyname(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("user").password("password").roles("USER");
    }
}

This code configures an in-memory user database with a single user and the specified password.

3. Per-URL access control: So where does Basic Authentication come from. By default, once you have enabled authentication the WebSecurityConfigurerAdapter provides this method for you:

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

You can override this method to do fine-grained access control for individual URLs depending on the user role:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/css/**", "/index").permitAll()
                .antMatchers("/user/**").hasRole("USER");
    }
}

4. Custom Authentication: To provide your own custom authentication logic, you need to do one of two things. Either, use the @Component annotation on an implementation of AuthenticationProvider and let Spring auto-wire it:

@Component
public class MyAuthProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        // your custom auth logic goes here
        // once the user is authenticated return the auth token,
        // otherwise throw an exception

        return new UsernamePasswordAuthenticationToken
            (username, password, authorities);
    }

    @Override
    public boolean supports(Class<? extends Object> authentication) {
        // you can limit the types of auth you respond to, so you may
        // limit the types to UsernamePasswordAuthenticationToken for
        // instance
        return true;
    }
}

Alternatively, in a configuration method, you can set your provider directly on the AuthenticationManagerBuilder (see #2 above).

Note that once you add a custom provider the authentication automatically switches from Basic to Form-based authentication. Spring Boot provides a default login form for you which you can override when you configure the HttpSecurity object (see #3 above)

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                    .antMatchers("/css/**", "/index").permitAll()
                    .antMatchers("/user/**").hasRole("USER")
                    .and()
                .formLogin().loginPage("/login").failureUrl("/login-error");
    }

5. Looking up users: To lookup user information, implement your custom UserDetailsService and wire it to your authentication provider:

@Component
public class MyAuthProvider implements AuthenticationProvider {

    @Autowired
    private SwanUserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        UserDetails user = userDetailsService.loadUserByUsername(username);

        ...
    }
}

The lookup method either succeeds or throws an exception if the user account is not found or if the password is not correct.

6. Custom Filtering: To implement a totally custom authentication, you can use request filters. This is explained in some detail here.

Notice that in none of the above we needed to implement our own

AuthenticationManager

. This post unnecessarily does so. Basically, Spring provides an implementation of the AuthenticationManager which is called ProviderManager which aggregates any number of AuthenticationProvider(s) and iterates over them succeeding if any of them authenticates the request (see diagram)

spring boot auth

Having gone through all these experiments, I am now ready to dive into what I really need to implement: OAuth2 authentication!

Leave a comment