UserDetailsService로 DB의 사용자 정보를 찾고 PasswordEncoder로 암호를 검증한다.
UserDetailsService는 사용자 관리를 담당한다.
JpaUserDetailsService가 데이터베이스 작업을 담당한다.
성공하고 반환된 세부 정보는 AuthenticationFilter에 의해 SecurityContext에 저장된다.
6.2. 사용자 관리 구현
@Configuration
public class ProjectConfig {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SCryptPasswordEncoder sCryptPasswordEncoder() {
return new SCryptPasswordEncoder(
16384, // CPU cost (N)
8, // Memory cost (r)
1, // Parallelization (p)
32, // Key length
64 // Salt length
);
}
}
public class CustomUserDetails implements UserDetails {
private final User user;
public CustomUserDetails(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getAuthorities().stream()
.map(a -> new SimpleGrantedAuthority(a.getName()))
.collect(Collectors.toList());
}
...
}
@Service
public class JpaUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public CustomUserDetails loadUserByUsername(String username) {
Supplier<UsernameNotFoundException> s =
() -> new UsernameNotFoundException("Problem during authentication!");
User u = userRepository.findUserByUsername(username).orElseThrow(s);
return new CustomUserDetails(u);
}
}
인증에 사용할 값들을 구현한다.
6.3. 맞춤형 인증 논리 구현
@Service
public class AuthenticationProviderService implements AuthenticationProvider {
@Autowired
private JpaUserDetailsService userDetailsService;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
private SCryptPasswordEncoder sCryptPasswordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
CustomUserDetails user = userDetailsService.loadUserByUsername(username);
return switch (user.getUser().getAlgorithm()) {
case BCRYPT -> checkPassword(user, password, bCryptPasswordEncoder);
case SCRYPT -> checkPassword(user, password, sCryptPasswordEncoder);
};
}
@Override
public boolean supports(Class<?> aClass) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass);
}
private Authentication checkPassword(CustomUserDetails user, String rawPassword, PasswordEncoder encoder) {
if (encoder.matches(rawPassword, user.getPassword())) {
return new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getAuthorities());
} else {
throw new BadCredentialsException("Bad credentials");
}
}
}
실제 인증 로직을 구현할 AuthenticationProvider를 구현한다.
@Configuration
public class ProjectConfig {
private final AuthenticationProvider authenticationProviderService;
public ProjectConfig(AuthenticationProvider authenticationProviderService) {
this.authenticationProviderService = authenticationProviderService;
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SCryptPasswordEncoder sCryptPasswordEncoder() {
return new SCryptPasswordEncoder(
16384, // CPU cost (N)
8, // Memory cost (r)
1, // Parallelization (p)
32, // Key length
64 // Salt length
);
}
@Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(List.of(authenticationProviderService));
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.formLogin(form -> form.defaultSuccessUrl("/main", true))
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated());
return http.build();
}
}