일반적인 Spring Security 내부 흐름입니다. 우리는 InMemoryUserDetailsManager를 사용하는 대신에 직접 loadByUsername()을 오버라이딩하여 사용해보도록 하겠습니다.
실습 - User 정보를 SQL 스크립트를 활용하여 수동 입력
Config - SecurityConfig
@Configuration
public class ProjectSecurityConfig {
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) ->
requests.requestMatchers("/myAccount", "/myBalance", "/myLoans", "/myCards").authenticated()
.requestMatchers("/notices", "/contact").permitAll()
);
http.formLogin(Customizer.withDefaults());
http.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
Repository - CustomerRepository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
List<Customer> findByEmail(String email);
}
Model - Customer
@Entity
@Getter
public class Customer {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String email;
private String pwd;
private String role;
}
Service - BankUserDetails
BankUserDetails는 UserDetailsService의 실질적인 구현체가 되고, 다른 UserDetailsManager의 영향을 받지 않게 됩니다.
@RequiredArgsConstructor
@Service
public class BankUserDetails implements UserDetailsService {
private final CustomerRepository customerRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String userName, password = null;
List<GrantedAuthority> authorities = null;
List<Customer> customers = customerRepository.findByEmail(username);
if(customers.isEmpty()) {
throw new UsernameNotFoundException("User details not found for the user : " + username);
} else {
userName = customers.get(0).getEmail();
password = customers.get(0).getPwd();
authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(customers.get(0).getRole()));
}
return new User(userName, password, authorities);
}
}
새로운 유저 등록을 허용하는 새 REST API 구축
SecurityConfig에서 permitAll()을 해주고, 다시 POST 요청을 보내도 에러가 발생합니다. 그 이유는 Spring Security 에서 제공하는 CSRF 보안 때문입니다. 기본 설정은 어떤 POST 요청이든 간에 DB나 백엔드 내부의 데이터를 수정하고자 하는 것이면 막도록 되어 있습니다. 일단 테스트를 위해 CSRF를 해제하겠습니다.
http.csrf(AbstractHttpConfigurer::disable)
그 결과, 정상적으로 유저 정보를 받아올 수 있었습니다.
실습 - Bcrypt PasswordEncoder를 사용한 새로운 유저 등록 (보안 강화)
passwordEncoder에서 기존에 넘겨주던 NoOpPasswordEncoder가 아닌 BCryptPasswordEncoder를 념겨주도록 코드를 수정해줍니다.
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
Postman을 통해 유저 자격 증명을 POST로 전송합니다.
결과 !!
NoOpPasswordEncoder를 이용한 방법은 문자열 그대로 저장되기 때문에 보안에 취약합니다. 하지만 BCryptPasswordEncoder의 경우 해싱 알고리즘을 이용한 방법으로 저장되기 때문에 상대적으로 안전한 모습을 볼 수 있습니다.