Backend/Spring Security

[Spring Security] PasswordEncoder

kimdozzi 2024. 6. 27. 20:36

일반적인 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의 경우 해싱 알고리즘을 이용한 방법으로 저장되기 때문에 상대적으로 안전한 모습을 볼 수 있습니다.