실전: 작고 안전한 웹 애플리케이션
6.1. 프로젝트 요구 사항과 설정
제품 정보와 사용자를 DB에 저장한다.
암호는 bcrypt나 scrypt로 해시된다.
AuthenticationFilter는 요청을 가로채서 인증 책임을 AuthenticationManager에 위임한다.
AuthenticationManager는 AuthenticationProvider로 요청을 인증한다.
AuthenticationProvider는 인증 로직과 관련된 모든 사항을 구현한다.
AuthenticationProvider를 구현하는 AuthenticationProviderService를 구현한다.
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();
}
}
구현한 프로바이더를 설정에 등록한다.
formLogin으로 등록하고 기본 경로를 설정한다.
Last updated
Was this helpful?