문제 상황
비밀 번호를 복호화 할 수 없게 하여 안전하게 보호하고 저장된 암호화 비교할 수 있다.
요구 사항
PasswordEncoder를 이해하고 비밀번호를 BCrypt 방식으로 생성하여 저장한다.
필요 개념
PasswordEncoder
PasswordEncoder는 Spring Security에서 제공하는 인터페이스로, 비밀번호를 안전하게 암호화하고 인증 시 비밀번호를 검증하는 역할을 한다. 비밀번호를 암호화하는 이유는 보안을 강화하기 위함이며, 특히 데이터베이스에 저장될 때 평문으로 저장하지 않고, 암호화된 형태로 저장하여 해킹 등의 보안 사고 발생 시에도 안전하게 비밀번호가 보호될 수 있도록 한다.
- 비밀번호 암호화 (Encoding):
- encode(CharSequence rawPassword) : 사용자가 입력한 평문 비밀번호를 암호화하여 반환한다.
- 이 과정에서 다양한 암호화 알고리즘을 사용할 수 있으며, Spring Security는 기본적으로 BCryptPasswordEncoder를 많이 사용한다.
- 사용자가 입력한 비밀번호를 암호화된 형태로 변환한다. 암호화된 비밀번호는 해시(hash) 함수로 처리되며, 이 과정에서 단방향 암호화를 사용하므로 복호화가 불가능하다. 즉, 비밀번호를 다시 원래의 값으로 되돌릴 수 없다.
- 비밀번호 비교 (Matching):
- matches(CharSequence rawPassword, String encodedPassword) : 입력된 평문 비밀번호와 저장된 암호화된 비밀번호를 비교하여 일치 여부를 확인한다.
- 저장된 암호화된 비밀번호와 사용자가 입력한 비밀번호를 비교할 때도 PasswordEncoder를 사용한다. 입력된 비밀번호를 다시 암호화한 후, 저장된 암호화된 비밀번호와 비교하는 방식이다.
BCryptPasswordEncoder
가장 많이 사용되는 암호화 방식으로, BCrypt 알고리즘을 사용한다. 이 알고리즘은 해시값을 생성할 때 고유한 "솔트(salt)"를 추가하여 동일한 비밀번호라도 매번 다른 해시값을 생성하게 한다. 또한, 해시 연산이 비교적 느려서 무차별 대입 공격(브루트 포스 공격)을 어렵게 만든다.
코드 설명
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/css/**", "/images/**", "/js/**", "/favicon.*", "/*/icon-*").permitAll()
.requestMatchers("/","/signup").permitAll()
.anyRequest().authenticated())
.formLogin(form -> form.loginPage("/login").permitAll());
// 로그인 페이지는 모든 사용자에게 접근을 허용하기 위해 permitAll()이 설정
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder(){
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
} //BCryptPasswordEncoder를 기본 암호화 방식으로 사용하면서도 다양한 암호화 방식을 유연하게 지원
@Bean
public UserDetailsService userDetailsService(){
UserDetails user = User.withUsername("user").password("{noop}1111").roles("USER").build();
return new InMemoryUserDetailsManager(user);
}
}
PasswordEncoderFactories.createDelegatingPasswordEncoder()를 사용하여 DelegatingPasswordEncoder를 생성하고 있다. 이 방식은 여러 암호화 알고리즘을 지원하며, 암호화된 비밀번호의 앞부분에 {id} 형식으로 어떤 암호화 방식을 사용했는지 명시하여 다양한 암호화 방식을 관리할 수 있다.
public class AccountServiceImpl implements AccountService {
private final AccountRepository accountRepository;
// PasswordEncoder는 Spring Security에서 제공하는 인터페이스로,
// 비밀번호를 안전하게 암호화하는 기능을 제공한다.
private final PasswordEncoder passwordEncoder;
@Override
public void signup(AccountDto accountDto) {
// Account 엔티티 객체를 생성하는 빌더 패턴을 사용하고 있다.
// userName, password, roles 필드를 AccountDto에서 가져와서 세팅한다.
Account account = Account.builder()
// userName은 클라이언트로부터 전달된 사용자 이름이다.
.userName(accountDto.getUserName())
// password는 사용자로부터 입력받은 비밀번호를 암호화한 값이다.
// passwordEncoder.encode()를 사용하여 비밀번호를 안전하게 저장할 수 있도록 암호화한다.
.password(passwordEncoder.encode(accountDto.getPassword()))
// roles는 사용자가 가질 권한(예: ROLE_USER, ROLE_ADMIN)을 나타낸다.
.roles(accountDto.getRoles())
.build();
// accountRepository를 통해 생성된 Account 객체를 데이터베이스에 저장한다.
// 이 과정에서 사용자 정보와 암호화된 비밀번호가 저장된다.
accountRepository.save(account);
}
}