FC
FACADE
CODE
Lesson 4 of 28
FC
FACADE
CODE

Learn Spring Security

Contents

01. Bootstrap The Application
1. Introduction
2. Install Spring Security
3. Enable Basic Auth
4. Authentication with AppUser
5. Password Encoder
02. Web Layer Security - RBAC
6. Permit Public APIs
7. Role Based Authorization
8. Disable CSRF
9. Current Authenticated User
03. Web Layer Security - PBAC
10. Permission Based Authorization
11. Define Permissions
12. Assign Permissions
13. Remove Role Based Access
04. Service Layer Security
14. PreAuthorize
15. PostAuthorize
16. Authorize Using Spring Beans
05. Domain Object Instance Security
17. Domain Object Instance Security
18. PermissionEvaluator Interface
19. PermissionEvaluator Strategy
20. DB Backed UserDetailsService
06. Token Based Authentication
21. Basic Authentication Revisited
22. Generate Token
23. Persist Token
24. Verify Token
25. Invalidate Token
07. Token Based Authentication and Authorization
26. JSON Web Token
27. Generate JWT
28. Verify JWT

Authentication with AppUser

So far we have authenticated using the default user and the random password provided by Spring Security. But as in any web applications we want to authenticate using our application users, which were created in the database by AppDataInitialiser when the application bootstraps. Let's see how we can do this using one of the implementations of UserDetailsService provided by Spring Security.

InMemoryUserDetailsManager

Spring Security uses UserDetailsService as the core interface to load user-specific data by username, and it requires only one read-only method.


public interface UserDetailsService {
    UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException;
}

InMemoryUserDetailsManager is one of the implementations provided by Spring Security. It is a non-persistant implementation backed by an in-memory map mainly intended for testing and demonstration purposes only. It accepts a collection of UserDetails objects, which is the same as the return type of the above loadUserByUsername() method.

Create UserDetails from database

Before registering InMemoryUserDetailsManager bean, let's create a collection of UserDetails objects. We can do this by fetching all the AppUser records from the database, map them all with User class and return the collection from a @Service method like below.


@Service
public class DbUserDetailsService {
    @Autowired
    private AppUserRepository appUserRepository;

    public List<UserDetails> getAllUserDetails() {
        return appUserRepository.findAll()
                .stream()
                .map(appUser -> User.builder()
                        .username(appUser.getUsername())
                        .password(String.format("{noop}%s", appUser.getPassword()))
                        .authorities(Collections.EMPTY_SET)
                        .build()
                )
                .collect(Collectors.toList());
    }
}

User is one of the implementations of UserDetails interface with a convenient Builder class. Here we have mapped username and password only and left the authorities as an empty Set as we are focusing only on authenticating application users for now.

We must provide the correct password encoding schema used to encode the passwords stored in the database. We can do this by prefixing each password with the appropriate string literal representing the PasswordEncoder. In our case we have not encrypted the passwords in the database, we are prefixing it with {noop}. This prefix tells Spring Security to use NoOpPasswordEncoder for password verification while authenticating the application user.

Register InMemoryUserDetailsManager Bean

Now with the list of UserDetails objects in hand, we can create and register an InMemoryUserDetailsManager bean inside a new @Configuration class like below.


@Configuration
public class SecurityBean {
    @Autowired
    private DbUserDetailsService dbUserDetailsService;

    @Bean
    public UserDetailsService userDetailsService() {
        return new InMemoryUserDetailsManager(
                dbUserDetailsService.getAllUserDetails()
        );
    }
}

Verify authentication using AppUser

When we restart the application, we can notice Spring Security is no longer generating the random password. And it no longer creates the default user also. It uses InMemoryUserDetailsManager bean to hold the list of UserDetails mapped from the AppUser records in the database.

We can use any of these AppUser from the database to authenticate in order to access the secured REST APIs. Let's use Bob and his credential to access the ListStudents API in Postman using Basic Auth.

List of all student records returned for the application user (Bob)
List of all student records returned for the application user (Bob)