Spring boot/Spring Security

Password Storage

metamong-data 2024. 7. 17. 12:20
728x90
반응형

개요

Spring Security에 있는 PasswordEncoder를 사용하기 위해 알아보고자 한다.

  • Spring Security의 PasswordEncoder인터페이스는 비밀번호를 안전하게 저장하기 위해 비밀번호의 단방향 변환을 수행하는 데 사용됩니다.
  • PasswordEncoder단방향 변환인 경우 비밀번호 변환이 양방향이어야 하는 경우(예: 데이터베이스에 인증하는 데 사용되는 자격 증명을 저장하는 경우)에는 유용하지 않습니다.
  • 일반적으로 PasswordEncoder인증 시 사용자가 제공한 비밀번호와 비교해야 하는 비밀번호를 저장하는 데 사용됩니다.

DelegatingPasswordEncoder

기본 PasswordEncoder가 BCryptPasswordEncoder와 비슷해질 것으로 예상할 수 있지만 이는 아래의 세 가지 실제 문제를 무시합니다.

  1. 많은 애플리케이션은 쉽게 마이그레이션할 수 없는 오래된 비밀번호 인코딩을 사용합니다.
  2. 비밀번호 저장에 대한 모범 사례가 다시 변경될 예정입니다.
  3. 프레임워크로서 Spring Security는 빈번하게 중대한 변경을 할 수 없습니다.

대신 Spring Security는 다음 DelegatingPasswordEncoder을 통해 모든 문제를 해결할 수 있습니다.

  • 현재 비밀번호 저장 권장 사항을 사용하여 비밀번호가 인코딩되었는지 확인
  • 최신 및 레거시 형식으로 비밀번호 검증 허용
  • 향후 인코딩 업그레이드를 허용합니다.

DelegatingPasswordEncoder: 를 사용하면 아래처럼 쉽게 인스턴스를 구성할 수 있습니다. PasswordEncoderFactories.

기본 DelegatingPasswordEncoder 생성 방법

PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

또는, 아래처럼 사용자 정의 인스턴스를 직접 만들수 있습니다.

사용자 정의 DelegatingPasswordEncoder 생성

String idForEncode = "bcrypt";
Map encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());
encoders.put("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());
encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());
encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());
encoders.put("sha256", new StandardPasswordEncoder());

PasswordEncoder passwordEncoder = new DelegatingPasswordEncoder(idForEncode, encoders);

기본 사용 방법

withDefaultPasswordEncoder 예제

UserDetails user = User.withDefaultPasswordEncoder()
  .username("user")
  .password("password")
  .roles("user")
  .build();
System.out.println(user.getPassword());
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

여러 사용자를 생성하는 경우 아래와 같이 빌더를 재사용할 수도 있습니다.

withDefaultPasswordEncoder 빌더 재 사용

UserBuilder users = User.withDefaultPasswordEncoder();
UserDetails user = users
  .username("user")
  .password("password")
  .roles("USER")
  .build();
UserDetails admin = users
  .username("admin")
  .password("password")
  .roles("USER","ADMIN")
  .build();

위와 같이 사용할 경우 ****저장된 암호는 hash 되지만 암호는 여전히 메모리 및 컴파일된 소스 코드에 노출됩니다. 따라서 프로덕션 환경에서는 여전히 안전하지 않은 것으로 간주됩니다. 프로덕션의 경우 암호를 외부에서 Hash해야 합니다.

BCryptPasswordEncoder

구현 BCryptPasswordEncoder은 널리 지원되는 bcrypt 알고리즘을 사용하여 암호를 해시합니다. 암호 해독에 대한 저항력을 높이기 위해 bcrypt는 의도적으로 느립니다.

다른 적응 형 단 방향 함수와 마찬가지로 시스템에서 암호를 확인하는 데 약 1초가 걸리도록 조정해야 합니다.

기본 구현은 BCryptPasswordEncoderJavadoc에 언급된 대로 강도 10을 사용합니다.

암호를 확인하는 데 약 1초가 걸리도록 자신의 시스템에서 강도 매개변수를 조정하고 테스트하는 것이 좋습니다.

// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));

Argon2PasswordEncoder

구현은  Argon2PasswordEncoder 알고리즘을 사용하여 비밀번호를 해싱합니다.

Argon2는 비밀번호 해싱 대회 에서 우승했습니다 .

사용자 지정 하드웨어에서 비밀번호 크래킹을 막기 위해 Argon2는 많은 양의 메모리를 필요로 하는 의도적으로 느린 알고리즘입니다.

다른 적응 형 단 방향 함수와 마찬가지로 시스템에서 비밀번호를 확인하는 데 약 1초가 걸리도록 조정해야 합니다. 현재 구현에는 BouncyCastle이 필요합니다.

// Create an encoder with all the defaults
Argon2PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));

Pbkdf2PasswordEncoder

구현은 Pbkdf2PasswordEncoder 알고리즘을 사용하여 비밀번호를 Hash합니다.

비밀번호 해독을 막기 위해 PBKDF2는 의도적으로 느린 알고리즘입니다.

다른 적응 형 단 방향 함수와 마찬가지로 시스템에서 비밀번호를 확인하는 데 약 1초가 걸리도록 조정해야 합니다.

이 알고리즘은 FIPS 인증이 필요할 때 좋은 선택입니다.

// Create an encoder with all the defaults
Pbkdf2PasswordEncoder encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));

SCryptPasswordEncoder

구현은 SCryptPasswordEncoder 알고리즘을 사용하여 비밀번호를 해시합니다.

사용자 지정 하드웨어에서 비밀번호 크래킹을 막기 위해 scrypt는 많은 양의 메모리를 필요로 하는 의도적으로 느린 알고리즘입니다.

다른 적응 형 단 방향 함수와 마찬가지로 시스템에서 비밀번호를 확인하는 데 약 1초가 걸리도록 조정해야 합니다.

// Create an encoder with all the defaults
SCryptPasswordEncoder encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));

비밀번호 저장 구성

Spring Security는 기본적으로 DelegatingPasswordEncoder를 사용합니다 . 그러나 PasswordEncoderSpring 빈으로 노출하여 이를 사용자 정의할 수 있습니다.

@Bean
public static NoOpPasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
}

//XML 구성에는 NoOpPasswordEncoder빈 이름이 필요합니다 passwordEncoder.

비밀번호 구성 변경

사용자가 비밀번호를 지정할 수 있는 대부분의 애플리케이션에는 해당 비밀번호를 업데이트하는 기능도 필요합니다.

비밀번호 변경을 위한 잘 알려진 URL은 비밀번호 관리자가 주어진 애플리케이션에 대한 비밀번호 업데이트 종 단점을 검색할 수 있는 메커니즘을 나타냅니다.

애플리케이션의 비밀번호 변경 엔드포인트가 이면 /change-passwordSpring Security를 아래와 같이 구성할 수 있습니다.

http
    .passwordManagement(Customizer.withDefaults())

그런 다음 비밀번호 관리자가 해당 Endpoint로 이동하면 /.well-known/change-passwordSpring Security가 해당 Endpoint를 리디렉션합니다 /change-password.

또는 엔드포인트가 다른 것이라면 /change-password다음과 같이 지정할 수도 있습니다.

http
    .passwordManagement((management) -> management
        .changePasswordPage("/update-password")
    )

위의 구성에서 비밀번호 관리자가 /.well-known/change-password으로 이동하면 Spring Security가 ./update-password 으로 리디렉션됩니다.

참고 : https://docs.spring.io/spring-security/reference/features/authentication/password-storage.html

728x90