Skip to content

fix: EgovCrnCheckValidation의 체크디지트 가중치 적용 범위와 개행 입력 처리 수정 - #292

Open
z3rotig4r wants to merge 3 commits into
eGovFramework:mainfrom
z3rotig4r:fix/crn-check-weight-digit
Open

fix: EgovCrnCheckValidation의 체크디지트 가중치 적용 범위와 개행 입력 처리 수정#292
z3rotig4r wants to merge 3 commits into
eGovFramework:mainfrom
z3rotig4r:fix/crn-check-weight-digit

Conversation

@z3rotig4r

Copy link
Copy Markdown
Contributor

문제

EgovCrnCheckValidation의 가중치 배열이 {1, 3, 7, 1, 3, 7, 1, 3, 5, 1} 열 개로 선언되어 있어 반복문이 열 번째 자리까지 순회합니다. 열 번째 자리는 검증 대상인 체크디지트 자신이므로 검증해야 할 값이 가중합에 섞여 들어갑니다. 공통컴포넌트 EgovNumberCheckUtil.checkCompNumber(2009년부터 운영)는 앞 아홉 자리에만 가중치를 적용합니다.

그래서 판정이 규격과 어긋납니다. 유효한 사업자등록번호 1만 건 중 9,026건이 거부되고 무효 번호 8,960건이 승인됩니다(규격 대비 일치율 82%). 체크디지트가 가중합에 한 번 더 더해지므로 유효한 번호는 체크디지트가 0인 경우에만 통과합니다.

패턴 검사에도 결함이 있습니다. matcher.find()가 최종 개행 앞의 매치를 허용하므로 "1248100998\n"이 열 자리 검사를 통과합니다. 이후 Integer.parseInt"8\n"을 파싱하며 NumberFormatException을 던집니다. 검증기가 false를 돌려주는 대신 예외가 그대로 전파되므로 400이 아닌 500으로 응답합니다.

수정

가중치 배열을 {1, 3, 7, 1, 3, 7, 1, 3, 5} 아홉 개로 줄여 앞 아홉 자리에만 적용했습니다. 체크디지트는 마지막 비교 대상으로만 남습니다. 이어서 matcher.find()matcher.matches()로 바꿔 문자열 전체가 열 자리 숫자와 일치할 때만 통과시키도록 했습니다. 커밋은 가중치 수정·테스트 추가·개행 수정 세 개로 나눠 두었습니다.

검증

  • 수정본은 EgovNumberCheckUtil.checkCompNumber와 알고리즘이 같습니다. 무작위 20만 건을 대조한 결과 판정이 완전히 일치했습니다.
  • matches()로 바꾼 뒤에도 정상 입력의 판정은 달라지지 않았습니다. 대조 집합의 승인 건수가 10,132건·10,000건으로 전환 전후 같았습니다.
  • mvn -o clean test로 해당 모듈 테스트 20건이 실패 없이 끝납니다. 이 가운데 8건이 이번에 추가한 reactive 검증기 8종의 시맨틱 테스트입니다.
  • 가중치 배열이나 matches()를 수정 전으로 되돌리면 추가한 테스트가 각각 실패합니다.

영향 범위

  • 수정 범위는 EgovCrnCheckValidation 한 파일입니다. @EgovCrnCheck의 판정 결과는 바뀝니다. 기존에 통과했던 무효 번호는 거부되고 거부됐던 유효 번호는 통과합니다. 판정이 규격에 맞게 바로잡히는 변화이므로 호출부는 손대지 않아도 됩니다.
  • EgovCnCheckValidation·EgovRrnCheckValidationfind()를 쓰고 있어 개행이 붙은 입력에서 같은 NumberFormatException이 납니다. 휴대전화·일반전화·영문·한글 검증기는 개행이 붙은 입력을 예외 없이 통과시킵니다. 이 PR은 사업자등록번호로 범위를 한정했고 나머지는 별도 PR로 분리하겠습니다.
  • 주민등록번호 테스트는 실제 개인 식별번호를 쓰지 않기 위해 존재할 수 없는 날짜(13월 32일)에 체크섬만 맞춘 합성값을 씁니다. 이 검증기는 패턴과 체크섬만 검증하고 날짜 유효성은 보지 않으므로 이 값을 유효로 판정합니다.
  • 날짜 유효성 검사가 없다는 점은 공통컴포넌트 checkJuminNumber와의 차이입니다. 동작이 바뀌는 범위가 넓어 이 PR에서는 다루지 않았습니다.

EgovCrnCheckValidation의 가중치 배열이 10개({1,3,7,1,3,7,1,3,5,1})여서
루프가 검증 대상인 10번째 체크디지트까지 가중합에 포함시켰다.
국세청 사업자등록번호 알고리즘은 앞 9자리에만 가중치를 적용하므로
배열을 9개로 줄인다.

수정 전에는 실제 유효 번호가 거부됐다(124-81-00998, 220-81-62517,
101-81-16293 모두 false). 무작위 10,000건을 국세청 규격과 대조하면
수정 전 8,218건(82.2%) 일치 -> 수정 후 10,000건(100%) 일치.
기존 ReactiveValidatorsNullSafeTest는 null 가드만 검증해
유효/무효 값 판정에 대한 회귀 그물망이 없었다.
사업자등록번호·법인등록번호·주민등록번호·휴대전화·일반전화·영문·한글·
비밀번호 검증기의 판정을 실제 값으로 검증한다.
사업자등록번호는 체크디지트 수정에 대한 회귀 테스트를 포함한다.

주민등록번호는 실제 개인 식별번호를 쓰지 않고, 존재할 수 없는 날짜에
체크섬만 규격에 맞춘 합성값을 사용한다.
패턴 검사에 matcher.find()를 사용하면 정규식의 $가 입력 끝뿐 아니라
마지막 개행 앞에서도 매치되므로 "1248100998\n" 같은 값이 10자리 검사를
통과한다. 이후 Integer.parseInt(mValue.substring(9))가 "8\n"을 파싱해
NumberFormatException을 던지고, ConstraintValidator에서 예외가 나가면
false 판정이 아닌 ValidationException으로 전파되어 400 대신 500 응답이
된다. matcher.matches()로 바꿔 입력 전체가 패턴과 일치할 때만 통과시킨다.

정상 입력 판정은 바뀌지 않는다(무작위 10,000건 국세청 규격 대조 100%
일치 유지). 개행·CRLF 입력이 예외 없이 false가 되는 단언을 테스트에
추가하고, 주민등록번호 합성 픽스처가 존재할 수 없는 날짜인 이유
(이 검증기는 패턴과 체크섬만 검증하고 날짜 유효성은 검증하지 않는다)를
주석과 단언 메시지에 명시했다.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant