본문 바로가기

SPRING/SpringSecurity

[스프링 시큐리티 완전 정복| 실전 프로젝트 | 회원 인증 시스템] 로그인, 회원가입

문제 상황

 

 

 

비밀 번호를 복호화 할 수 없게 하여 안전하게 보호하고 저장된 암호화 비교할 수 있다.

 

 


요구 사항

 

 



PasswordEncoder를 이해하고 비밀번호를 BCrypt 방식으로 생성하여 저장한다.

 

필요 개념

 

 

PasswordEncoder

PasswordEncoder는 Spring Security에서 제공하는 인터페이스로, 비밀번호를 안전하게 암호화하고 인증 시 비밀번호를 검증하는 역할을 한다. 비밀번호를 암호화하는 이유는 보안을 강화하기 위함이며, 특히 데이터베이스에 저장될 때 평문으로 저장하지 않고, 암호화된 형태로 저장하여 해킹 등의 보안 사고 발생 시에도 안전하게 비밀번호가 보호될 수 있도록 한다.

  1. 비밀번호 암호화 (Encoding):
    • encode(CharSequence rawPassword) : 사용자가 입력한 평문 비밀번호를 암호화하여 반환한다.
    • 이 과정에서 다양한 암호화 알고리즘을 사용할 수 있으며, Spring Security는 기본적으로 BCryptPasswordEncoder를 많이 사용한다.
  2. 사용자가 입력한 비밀번호를 암호화된 형태로 변환한다. 암호화된 비밀번호는 해시(hash) 함수로 처리되며, 이 과정에서 단방향 암호화를 사용하므로 복호화가 불가능하다. 즉, 비밀번호를 다시 원래의 값으로 되돌릴 수 없다.
  3. 비밀번호 비교 (Matching):
    • matches(CharSequence rawPassword, String encodedPassword) : 입력된 평문 비밀번호와 저장된 암호화된 비밀번호를 비교하여 일치 여부를 확인한다.
  4. 저장된 암호화된 비밀번호와 사용자가 입력한 비밀번호를 비교할 때도 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);
    }
}