PasswordEncoder는 인증 프로세스에서 암호가 유효한지를 확인한다. 인코딩된 암호를 저장하며 아무도 암호를 읽을 수 없게 해시를 저장하는 것이 좋다.
public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
- encode 메서드는 주어진 문자열을 변환해 반환한다. 주어진 암호의 해시를 제공하거나 암호화를 수행한다.
- matches 메서드에서 인코딩된 문자열이 원시 암호와 일치하는지 확인한다. 지정된 암호를 인증 프로세스에서 알려진 자격 증명의 집합을 대상으로 비교한다.
- upgradeEncoding은 기본값이 false이며, true를 반환하도록 재정의하면 인코딩된 암호를 보안 향상을 위해 다시 인코딩한다.(원래 암호를 알아내기 더 어려울 수 있으나 모호하기 때문에 추천 X)
2장에서 나온 NoOpPasswordEncoder는 가장 직관적으로 암호를 일반 텍스트로 간주하였다.
public class PlainTextPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
return rawPassword.toString(); // 암호를 변경하지 않고 그대로 반환
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return rawPassword.toString().equals(encodedPassword); // 두 문자열이 같은지 확인
}
}
해싱 알고리즘 SHA-512를 이용하는 PasswordEncoder의 간단한 구현
package com.studiop.ssiach2ex1;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class Sha512PasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
return hashWithSHA512(rawPassword.toString());
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
String hashedPassword = encode(rawPassword);
return encodedPassword.equals(hashedPassword);
}
private String hashWithSHA512(String input) {
StringBuilder result = new StringBuilder();
try {
MessageDigest md = MessageDigest.getInstance("SHA-512");
byte[] digested = md.digest(input.getBytes());
for (int i = 0; i < digested.length; i++) {
result.append(Integer.toHexString(0xFF & digested[i]));
}
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Bad algorithm");
}
return result.toString();
}
}
스프링 시큐리티에 구현되어 있는 PasswordEncoder
- NoOpPasswordEncoder : 암호를 인코딩하지 않고 일반 텍스트로 유지. 예제 용도로만 사용해야 하며 실제 시나리오에서는 절대 쓰지 말아야 한다.
- StandardPasswordEncoder : SHA-256을 이용해 암호를 해시한다. 강도가 약한 구식이라서 새 구현에는 쓰지 말아야 한다.
- Pbkdf2PasswordEncoder : PBKDF2를 이용한다.
- BCryptPasswordEncoder : bcrypt 강력 해싱 함수로 암호를 인코딩한다.
- SCryptPasswordEncoder : scrypt 해싱 함수로 암호를 인코딩한다.
간단한 예시(타입을 인터페이스로 지정)
PasswordEncoder p = NoOpPasswordEncoder.getInstance();
PasswordEncoder p = new StandardPasswordEncoder();
PasswordEncoder p = new StandardPasswordEncoder("secret");
SecureRandom s = SecureRandom.getInstanceStrong();
PasswordEncoder p = new BCryptPasswordEncoder(4, s);
...
* DelegatingPasswordEncoder 전략
- 특정 애플리케이션 버전부터 인코딩 알고리즘이 변경된 경우, 현재 사용되는 알고리즘에서 취약성이 발견되어서 신규 등록 사용자의 자격 증명을 변경하고 싶지만, 기존 자격 증명을 변경하기 쉽지 않은 경우, 여러 종류의 해시를 지원해야 할 때의 대안. 자체 인코딩 알고리즘을 구현하는 대신 다른 구현 인스턴스에 작업을 위임함.
@Configuration
public class ProjectConfig {
@Bean
public PasswordEncoder passwordEncoder() {
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("bcrypt", new BCryptPasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
return new DelegatingPasswordEncoder("bcrypt", encoders);
}
}
- 해시는 해당 해시를 의미하는 알고리즘의 이름을 나타내는 접두사로 시작한다. 암호의 접두사를 기준으로 올바른 PasswordEncoder 구현에 작업을 위임함.
접두사가 없으면 DelegatingPasswordEncoder 인스턴스를 만들 때 첫 번째 매개변수로 지정한다.
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
// 모든 표준 제공 PasswordEncoder의 구현에 대한 맵을 가진 DelegatingPasswordEncoder를 생성하는 방법을 제공함.
// PasswordEncoderFactories 클래스에는 bcrypt가 기본 인코더인 DelegatingPasswordEncoder의 구현을 반환하는
// 정적 메서드 createDelegatingPasswordEncoder() 가 있음
인코딩, 암호화, 해싱 개념
인코딩(Encoding) : 주어진 입력에 대한 모든 변환. 문자열을 뒤집는 함수 x가 있을 때 x -> y를 ABCD에 적용하면 DCBA가 나옴.
암호화(Encryption) : 출력을 얻기 위해 입력 값과 키를 모두 지정하는 특정한 유형의 인코딩. 키를 이용하면 나중에 누가 출력에서 입력을 얻는 함수를 호출할 수 있는지 선택할 수 있음(키를 소유하는 자에 따라 할 수 있는지가 선택되니까). (x, k) -> y. 이며, (y, k) -> x 를 역함수 복호화(Reverse Function Decryption)라고 한다.
암호화에서 쓰는 키와 복호화에 쓰는 키가 같으면 대칭 키라고 하며, (x, k1) -> y, (y, k2) -> x 처럼 암호화와 복호화에 다르키를 쓰면 이를 비대칭 키(Asymmetric Key)라고 하며 (k1, k2)를 키 쌍이라고 하며 k1을 공개 키(Public Key)라고 하고 k2를 개인 키(private key)라고 한다. 이와 같이 개인 키의 소유자는 데이터를 복호화할 수 있다.
해싱(Hashing) : 함수가 한 방향으로만 작동하는 특정한 유형의 인코딩. 출력 y에서 입력 x를 얻을 수 없지만, 출력 y가 입력 x에 해당하는지는 알 수 있음. x -> y, (x, y) -> boolean. 때때로 입력에 임의의 값을 추가할 수도 있는데, (x, k) -> y. 이 값을 솔트(Salt)라고 한다. 솔트는 함수를 더 강하게 만들어 결과에서 입력을 얻는 역함수의 적용 난도를 높인다.
UserDetails | 스프링 시큐리티가 관리하는 사용자를 나타냄 |
GrantedAuthority | 애플리케이션의 목적 내에서 사용자에게 허용되는 작업을 정의 (읽기, 쓰기, 삭제 등) |
UserDetailsSerivce | 사용자 이름으로 사용자 세부 정보를 검색하는 객체를 나타냄 |
UserDetailsManager | UserDeatilsService의 더 구체적인 계약. 사용자 이름으로 사용자를 검색하는 것 외에도 사용자 컬렉션이나 특정 사용자를 변경할 수도 있음 |
PasswordEncoder | 암호를 암호화 또는 해시하는 방법과 주어진 인코딩된 문자열을 일반 텍스트 암호와 비교하는 방법을 지정 |
4.2 스프링 시큐리티 암호화 모듈에 관한 추가 정보
- 암호화 및 복호화 함수와 키 생성 기능은 자바 언어에서 기본 제공되지 않아 스프링 시큐리티에서 자체 솔루션을 제공함
키 생성기 : 해싱 및 암호화 알고리즘을 위한 키를 생성하는 객체 (StringKeyGenerator 인터페이스)
암호기 : 데이터를 암호화 및 복호화하는 객체 (TextEncryptor 인터페이스)
하략... 책 109페이지
요약
- PasswordEncoder는 인증 논리에서 암호를 처리하는 가장 중요한 책임을 담당함
- 스프링 시큐리티는 해싱 알고리즘에 여러 대안을 제공하므로 필요한 구현을 선택하기만 하면됨
- 스프링 시큐리티는 암호화 모듈(SSCM)에는 키 생성기와 암호기를 구현하는 여러 대안이 있음
'스프링 > 스프링 시큐리티 인 액션 (도서 정리)' 카테고리의 다른 글
스프링 시큐리티 - 5. 인증 구현 (0) | 2025.04.30 |
---|---|
스프링 시큐리티 - 3. 사용자 관리 (0) | 2025.04.29 |
스프링 시큐리티 - 2. 기본 프로젝트 만들어보기 (1) | 2025.04.29 |
스프링 시큐리티 - 1. 보안 개념 (0) | 2025.04.28 |