Loading Users
In our previous lesson, we added Spring Security to protect our blog platform's endpoints.
Now we'll implement user loading functionality to enable authentication based on our existing User entity.
This implementation will bridge the gap between Spring Security's user management and our domain model.
Creating the UserDetails Class
The BlogUserDetails class adapts our domain User entity to Spring Security's authentication system:
package com.devtiro.blog.security;
import com.devtiro.blog.domain.entities.User;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
@Getter
@RequiredArgsConstructor
public class BlogUserDetails implements UserDetails {
private final User user;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("ROLE_USER"));
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getEmail();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public UUID getId() {
return user.getId();
}
}Extending the User Repository
The UserRepository needs a method to find users by their email addresses as that's what the user will use to log in:
@Repository
public interface UserRepository extends JpaRepository<User, UUID> {
Optional<User> findByEmail(String email);
}Implementing the UserDetails Service
The BlogUserDetailsService loads user data and converts it to Spring Security's UserDetails format:
package com.devtiro.blog.services.impl;
import com.devtiro.blog.domain.BlogUserDetails;
import com.devtiro.blog.domain.entities.User;
import com.devtiro.blog.repositories.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@RequiredArgsConstructor
public class BlogUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + email));
return new BlogUserDetails(user);
}
}Handling Authentication Errors
Let's add handling for authentication failures to our ErrorController:
@ExceptionHandler(BadCredentialsException.class)
public ResponseEntity<ApiErrorResponse> handleBadCredentialsException(BadCredentialsException ex) {
ApiErrorResponse error = ApiErrorResponse.builder()
.status(HttpStatus.UNAUTHORIZED.value())
.message("Incorrect username or password")
.build();
return new ResponseEntity<>(error, HttpStatus.UNAUTHORIZED);
}Summary
- Created
BlogUserDetailsto adapt our domain user model to Spring Security - Extended
UserRepositorywith email-based user lookup - Implemented
BlogUserDetailsServiceto load users during authentication - Added error handling for authentication failures