Skip to main content
Advertisement

11.2 BCrypt Encryption and UserDetailsService Implementation

Passwords must never be stored in plain text in a database. Additionally, we need to bridge Spring Security with the user information stored in the database.

1. PasswordEncoder and BCrypt

BCrypt is a widely used algorithm for password hashing. It provides strong security and automatically generates a Salt to defend against rainbow table attacks.

Registering PasswordEncoder Bean

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

2. UserDetails and UserDetailsService

Interfaces must be implemented so that Spring Security can understand user information.

  • UserDetails: An object containing user information such as username, password, and authorities.
  • UserDetailsService: A service that retrieves user information from a repository (like a database) and returns UserDetails.

Example of Custom UserDetailsService Implementation

@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

private final UserRepository userRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByEmail(username)
.map(user -> User.builder()
.username(user.getEmail())
.password(user.getPassword()) // Must be BCrypt hashed in DB
.roles(user.getRole().name())
.build())
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
}
}

3. Password Encryption during Signup

The PasswordEncoder must be used during the user registration process.

public void signup(UserDto dto) {
String encodedPassword = passwordEncoder.encode(dto.getPassword());
User user = User.builder()
.email(dto.getEmail())
.password(encodedPassword)
.role(Role.USER)
.build();
userRepository.save(user);
}

🎯 Key Points

  • Passwords must be encrypted using a one-way hash algorithm like BCrypt.
  • UserDetailsService acts as the connection point between Spring Security and the database.
  • Validate user existence and return a UserDetails object in the loadUserByUsername method.

11.4 Authentication Flow and Practical Example

Authentication in Spring Security is achieved through the collaboration of several components. Here, we'll trace the full lifecycle of the most common Form Login (ID/PW) method.

1. Full Authentication Process (Mermaid)

sequenceDiagram
participant User as User(Client)
participant Filter as AuthenticationFilter
participant Manager as AuthenticationManager
participant Provider as AuthenticationProvider
participant Service as UserDetailsService
participant DB as Database

User->>Filter: 1. Login Request (username, password)
Filter->>Filter: 2. Create UsernamePasswordAuthenticationToken (unauthenticated)
Filter->>Manager: 3. Call authenticate(token)
Manager->>Provider: 4. Delegate authentication to a supporting Provider
Provider->>Service: 5. loadUserByUsername(username)
Service->>DB: 6. Query user info
DB-->>Service: Return User Entity
Service-->>Provider: 7. Return UserDetails object
Provider->>Provider: 8. Verify password (BCrypt comparison)
Provider-->>Manager: 9. Return authenticated Authentication object
Manager-->>Filter: 10. Authentication complete
Filter->>Filter: 11. Store info in SecurityContextHolder
Filter-->>User: 12. Success response/redirect

2. Roles of Key Components

  1. AuthenticationFilter: Intercepts the HTTP request, creates an unauthenticated Authentication object, and passes it to the Manager.
  2. AuthenticationManager: The main interface overseeing authentication. (Typically, the ProviderManager implementation is used.)
  3. AuthenticationProvider: Performs the actual business logic (e.g., ID/PW validation). Multiple providers can be registered to handle various authentication types (LDAP, DB, Social, etc.).
  4. UserDetailsService: Acts as the "data provider" that retrieves user information from a storage like a database.
  5. SecurityContextHolder: A storage area that keeps authenticated information accessible throughout the application.

3. Practical Example: Login API Implementation (JWT)

Following modern trends, here is a simple controller example that issues a JWT token after successful authentication.

LoginRequest DTO

public record LoginRequest(String email, String password) {}

AuthController

@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {

private final AuthenticationManager authenticationManager;
private final TokenProvider tokenProvider;

@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody LoginRequest request) {
// 1. Create an unauthenticated token
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(request.email(), request.password());

// 2. Start actual authentication (triggers Manager -> Provider -> Service flow)
// Throws exception if authentication fails
Authentication authentication = authenticationManager.authenticate(authenticationToken);

// 3. Store authentication info in context (optional but recommended)
SecurityContextHolder.getContext().setAuthentication(authentication);

// 4. Generate and return a JWT token based on authenticated info
String jwt = tokenProvider.createToken(authentication);
return ResponseEntity.ok(jwt);
}
}

4. Utilizing Authentication Info (@AuthenticationPrincipal)

Used when you want to directly access the logged-in user's information from a controller.

@GetMapping("/api/me")
public ResponseEntity<String> getMyInfo(@AuthenticationPrincipal UserDetails userDetails) {
return ResponseEntity.ok("Current User: " + userDetails.getUsername());
}

🎯 Key Points

  • Authentication goes through the stages of Filter -> Manager -> Provider -> Service.
  • The AuthenticationManager is the central role, while the Provider handles actual verification.
  • Once successful, user info is held in the SecurityContextHolder, making it accessible globally.
Advertisement