Securing the chat microservice

Okay, this chapter is titled Securing Your App with Spring Boot, yet we have spent a fair amount of time... NOT securing our app! That is about to change. Thanks to this little bit of restructuring, we can move forward with locking things down as desired.

Let's take a crack at writing some security policies, starting with the chat microservice:

    @EnableWebFluxSecurity 
    public class SecurityConfiguration { 
 
      @Bean 
      SecurityWebFilterChain springWebFilterChain(HttpSecurity http) { 
        return http 
            .authorizeExchange() 
                .pathMatchers("/**").authenticated() 
                .and() 
            .build(); 
      } 
    } 

The preceding security policy can be defined as follows:

  • @EnableWebFluxSecurity activates the Spring WebFlux security filters needed to secure our application
  • @Bean marks the one method as a bean definition
  • HttpSecurity.http() lets us define a simple set of authentication and authorization rules
  • In this case, every Spring WebFlux exchange (denoted by /**) must be authenticated
The .pathMatchers("/**").authenticated() rule is the first rule based upon URLs. It's also possible to put additional requirements at the method level, which we'll explore later in this chapter.

This is a nice beginning to define a security policy, but we need some way to track user data to authenticate against. To do so, we need a User domain object and a way to store such data. To minimize our effort at storing user information in a database, let's leverage Spring Data again.

First, we'll create a User domain object like this:

    @Data 
    @AllArgsConstructor 
    @NoArgsConstructor 
    public class User { 
 
      @Id private String id; 
      private String username; 
      private String password; 
      private String[] roles; 
    } 

This preceding User class can easily be described as follows:

  • @Data uses the Lombok annotation to mark this for getters, setters, equals, toString, and hashCode functions
  • @AllArgsConstructor creates a constructor call for all of the attributes
  • @NoArgsConstructor creates an empty constructor call
  • @Id marks this id field as the key in MongoDB
  • username, password, and roles are critical fields required to properly integrate with Spring Security, as shown further in the chapter
The names of these fields don't matter when it comes to integrating with Spring Security, as we'll soon see.

To interact with MongoDB, we need to create a Spring Data repository as follows:

    public interface UserRepository 
     extends Repository<User, String> { 
 
       Mono<User> findByUsername(String username); 
    } 

This is similar to the other repositories we have built so far in the following ways:

  • It extends Spring Data Commons' Repository, indicating that the domain type is User and the ID type is String
  • It has one finder needed for security lookups, findByUsername, which is returned as a reactive Mono<User>, signaling Spring Data MongoDB to use reactive MongoDB operations

With this handy repository defined, let's preload some user data into our system by creating an InitUsers class, as shown here:

    @Configuration 
    public class InitUsers { 
 
      @Bean 
      CommandLineRunner initializeUsers(MongoOperations operations) { 
        return args -> { 
          operations.dropCollection(User.class); 
 
          operations.insert( 
            new User( 
              null, 
              "greg", "turnquist", 
              new String[]{"ROLE_USER", "ROLE_ADMIN"})); 
          operations.insert( 
            new User( 
              null, 
              "phil", "webb", 
              new String[]{"ROLE_USER"})); 
 
          operations.findAll(User.class).forEach(user -> { 
            System.out.println("Loaded " + user); 
          }); 
        }; 
      } 
    } 

This preceding user-loading class can be described as follows:

  • @Configuration indicates this class contains bean definitions
  • @Bean marks the initializeUsers method as a Spring bean
  • initializeUsers requires a copy of the blocking MongoOperations bean defined by Spring Boot's MongoDB autoconfiguration code
  • The return type is CommandLineRunner, which we'll supply with a lambda function
  • Inside our lambda function, we drop the User based collection, insert two new users, and then print out the collection

Now, let's see how to put that to good use! To hook into Reactive Spring Security, we must implement its UserDetailsRepository interface. This interface is designed to look up a user record through any means necessary and bridge it to Spring Security as a Mono<UserDetails> return type. The solution can be found here:

    @Component 
    public class SpringDataUserDetailsRepository implements   
UserDetailsRepository { private final UserRepository repository; public SpringDataUserDetailsRepository(UserRepository
repository) { this.repository = repository; } @Override public Mono<UserDetails> findByUsername(String username) { return repository.findByUsername(username) .map(user -> new User( user.getUsername(), user.getPassword(), AuthorityUtils.createAuthorityList(user.getRoles()) )); } }

The previous code can be described as follows:

  • It injects a UserRepository we just defined through constructor injection
  • It implements the interface's one method, findByUsername, by invoking our repository's findByUsername method and then mapping it onto a Spring Security User object (which implements the UserDetails interface)
  • AuthorityUtils.createAuthorityList is a convenient utility to translate a String[] of roles into a List<GrantedAuthority>
  • If no such user exists in MongoDB, it will return a Mono.empty(), which is the Reactor equivalent of null
We map our MongoDB User domain object onto Spring Security's org.springframework.security.core.userdetails.User object to satisfy the UserDetails requirement. However, that doesn't mean we can't implement a custom version of this interface. Imagine we were building a medical tracking system and needed each patient record to contain a detailed profile. A custom implementation would allow us to fill in the critical fields while also adding all the other data needed to track a person.

By hooking MongoDB-stored users into Spring Security, we can now attempt to access the system.

When we try to access localhost:8080, we can expect a login prompt, as shown in this screenshot:

This popup (run from an incognito window to ensure there are no cookies or lingering session data) lets us nicely log in to the gateway.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.168.211