Learn Spring Security
Contents
Current Authenticated User
In the last chapter, you might have noticed the value for createdBy field is null in the newly created Course object. Of course, we did not send the user details in the request body for the createdBy field. As every request is authenticated, it makes sense to expect the requestor (Gru) details to get mapped automatically in the createdBy field.
SecurityContextHolder
At the heart of Spring Security's authentication model is SecurityContextHolder. It contains SecurityContext holding the details of who is authenticated in the form of Authentication object. It uses ThreadLocal to store these details to make it available for all the methods in the same thread through out the request.
Authentication
Authentication represents the token for an authentication request or for an authenticated principal. Technically it serves as the input and output for the one and only authenticate() method in AuthenticationManager interface.
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
}
As an authentication request it carries the username and password provided by the user where the isAuthenticated flag is set to false by default. Once the request has been processed by the AuthenticationManager it returns back the Authentication object as authenticated principal. As an authenticated principal it carries either the username or UserDetails and the collection of GrantedAuthority of the authenticated user where the isAuthenticated flag is set to true.
Let's create a Facade component to get this Authentication object as an authenticated principal from SecurityContextHolder.
@Component
public class AuthenticationFacade {
public Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
}
Find AppUser using Authentication object
As the variable type of createdBy field in the Course entity is AppUser, we have to retrieve the AppUser record from the database using the username found in the Authentication object.
Let's define findByUsername() method in AppUserRepository interface.
@Repository
public interface AppUserRepository extends JpaRepository<AppUser, Long> {
Optional<AppUser> findByUsername(String username);
}
Add a service method in UserService to get the AppUser object by username using the above method.
public AppUser get(String username) {
return appUserRepository.findByUsername(username)
.orElse(null);
}
Create course service
Now let's wire all the above pieces together in the create() service method defined in CourseService component, in order to update the createdBy field with the AppUser object before saving the new Course.
public Course create(Course newCourse) {
String username = authenticationFacade.getAuthentication().getName();
AppUser currentUser = userService.get(username);
newCourse.setCreatedBy(currentUser);
return courseRepo.save(newCourse);
}
Restart the application and send a POST request to CreateCourse API as Gru who has INSTRUCTOR role. And you can see the createdBy field automatically saved with the requestor details even though we have not sent it as part of the request body.