Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- Linux
- tibero
- nodejs
- JPA
- Java
- nginx
- springboot
- database
- dbeaver
- gson
- IntelliJ
- Windows
- Spring
- mybatis
- react
- Kubernetes
- gradle
- NCP
- BPMN
- VSCode
- maven
- kubectl
- Git
- JavaScript
- MySQL
- useEffect
- docker
- SAP
- LOG4J
- log4j2
Archives
- Today
- Total
두 손끝의 창조자
Hex-encoded string must have an even number of characters 본문
반응형
Spring Security PasswordEncoder Hex 디코딩 오류 해결
문제 상황
로그인 시 다음과 같은 오류가 발생했습니다:
java.lang.IllegalArgumentException: Hex-encoded string must have an even number of characters
at org.springframework.security.crypto.codec.Hex.decode(Hex.java:51)
at org.springframework.security.crypto.password.Pbkdf2PasswordEncoder.decode(Pbkdf2PasswordEncoder.java:221)
at org.springframework.security.crypto.password.Pbkdf2PasswordEncoder.matchesNonNull(Pbkdf2PasswordEncoder.java:212)원인 분석
1. 설정 파일의 비밀번호 형식
application.yml에 다음과 같이 {noop} prefix를 가진 비밀번호가 설정되어 있었습니다:
idp:
users:
- username: "admin"
password: "{noop}admin"
- username: "user"
password: "{noop}user"
2. PasswordEncoder 설정
PasswordEncoderConfig에서 Pbkdf2PasswordEncoder만 사용하도록 설정되어 있었습니다:
@Bean
Pbkdf2PasswordEncoder testPasswordEncoder() {
return new Pbkdf2PasswordEncoder(
"",
SALT_LENGTH,
ITERATIONS,
PBKDF2WithHmacSHA256
);
}
3. 문제점
{noop}prefix는NoOpPasswordEncoder를 의미하는 Spring Security의 표준 형식입니다- 하지만
Pbkdf2PasswordEncoder는 이 prefix를 인식하지 못하고,{noop}admin문자열을 Hex로 디코딩하려고 시도 - 결과적으로 "Hex-encoded string must have an even number of characters" 오류 발생
해결 방법
DelegatingPasswordEncoder를 사용하여 여러 인코더를 지원하도록 수정했습니다.
수정된 코드
package cothe.oidcidp.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import java.util.Map;
import static org.springframework.security.crypto.password.Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA256;
@Configuration
public class PasswordEncoderConfig {
public static final int SALT_LENGTH = 16;
public static final int ITERATIONS = 10;
@Bean
public PasswordEncoder passwordEncoder() {
Pbkdf2PasswordEncoder pbkdf2Encoder = new Pbkdf2PasswordEncoder(
"",
SALT_LENGTH,
ITERATIONS,
PBKDF2WithHmacSHA256
);
return new DelegatingPasswordEncoder(
"pbkdf2",
Map.of(
"noop", NoOpPasswordEncoder.getInstance(),
"pbkdf2", pbkdf2Encoder
)
);
}
}
주요 변경 사항
DelegatingPasswordEncoder 도입
- 여러 PasswordEncoder를 지원하는 위임 패턴 사용
- 비밀번호의 prefix에 따라 적절한 인코더 선택
인코더 매핑
{noop}→NoOpPasswordEncoder: 기존 평문 비밀번호 지원{pbkdf2}→Pbkdf2PasswordEncoder: 새로운 비밀번호는 PBKDF2로 인코딩
기본 인코더 설정
"pbkdf2"를 기본 인코더로 설정하여, prefix가 없는 새 비밀번호는 자동으로 PBKDF2로 인코딩
동작 원리
DelegatingPasswordEncoder의 동작 방식
비밀번호 저장 시
- prefix가 없으면 기본 인코더(
pbkdf2) 사용 {pbkdf2}...형식으로 저장
- prefix가 없으면 기본 인코더(
비밀번호 검증 시
{noop}admin→NoOpPasswordEncoder로 검증{pbkdf2}...→Pbkdf2PasswordEncoder로 검증- prefix가 없으면 기본 인코더로 처리
예시
// 기존 비밀번호 (application.yml에서 로드)
password: "{noop}admin" // NoOpPasswordEncoder로 검증
// 새로 생성되는 비밀번호
passwordEncoder.encode("newpassword")
// → "{pbkdf2}..." 형식으로 저장, Pbkdf2PasswordEncoder로 검증
추가 고려사항
NoOpPasswordEncoder Deprecation 경고
NoOpPasswordEncoder는 보안상의 이유로 deprecated되었습니다. 하지만 기존 데이터와의 호환성을 위해 유지했습니다.
향후 마이그레이션 계획:
- 기존
{noop}비밀번호를 사용자에게 비밀번호 변경 요청 - 또는 배치 작업으로 모든 비밀번호를
{pbkdf2}형식으로 재인코딩
보안 권장사항
- 프로덕션 환경에서는
NoOpPasswordEncoder사용을 피해야 합니다 - 가능한 한 모든 비밀번호를
Pbkdf2PasswordEncoder또는 더 강력한 인코더로 마이그레이션 {noop}prefix는 개발/테스트 환경에서만 사용
참고 자료
요약
- 문제:
Pbkdf2PasswordEncoder가{noop}prefix를 인식하지 못해 Hex 디코딩 오류 발생 - 해결:
DelegatingPasswordEncoder를 사용하여 여러 인코더 지원 - 결과: 기존
{noop}비밀번호와 새로운{pbkdf2}비밀번호 모두 정상 처리
반응형
Comments