Repository navigation
fix(onboarding): 비밀번호 규칙이 서버보다 느슨해 클라 통과분이 서버 400 — 길이·특수문자 등 4축 불일치 #1628
Description
작업 유형
bug — 버그·오동작
주 담당 모듈
onboarding — 온보딩
개요
클라가 통과시킨 비밀번호를 서버가 400 으로 되돌리는 구간이 있다. 클라 규칙이 서버 규칙보다 넓어서, 사용자는 「조건 충족」 표시를 다 받고 다음 단계로 넘어간 뒤 제출에서 실패한다.
기준: FE origin/develop 124547cb · PR #1624 f6000764 / BE origin/main 8e605f41.
| 정규식 | 길이 | 특수문자 | 영문 | |
|---|---|---|---|---|
FE OnboardingPasswordRule |
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,16}$ |
8~16 | 영숫자·개행이 아닌 모든 문자 | 대문자·소문자 각 1 |
BE PasswordValidation.REGEX |
^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,15}$ |
8~15 | @ $ ! % * # ? & 8종만 |
대소문자 무관 1자 |
BE 는 이 상수 하나를 SignupRequest · PasswordChangeRequest · PasswordFindRequest 세 곳에 @Pattern 으로 건다. @Pattern 은 Bean Validation 이라 컨트롤러 진입 전에 걸리고, GlobalExceptionHandler 가 1400 INVALID_INPUT_VALUE + 위반 메시지로 돌려준다.
실측
2026-08-31 새벽 KST, dev afternote.kro.kr. 계정을 만들거나 바꾸지 않는 경로만 썼다 — auth/password/find 는 미가입 이메일 + 무효 인증번호(000000), auth/sign-up 은 미인증 이메일. 다른 필드는 모두 Bean-valid 하게 두었으므로 1400 의 메시지는 비밀번호 위반을 가리킨다. 통과 케이스가 서비스 고유 코드(1219 미가입 이메일 / 1220 이메일 인증 필요)로 간다는 점이 비밀번호 게이트를 실제로 지났음을 보증한다.
1차 — 회원가입 화면에서 실제로 입력될 만한 값
| # | 입력 | 길이 | FE | password/find |
sign-up |
|---|---|---|---|---|---|
| 1 | Password1! |
10 | 통과 | 1219 | 1220 |
| 2 | Password12345!a |
15 | 통과 | 1219 | 1220 |
| 3 | Password12345!ab |
16 | 통과 | 1400 | 1400 |
| 4 | Password123-abc |
15 | 통과 | 1400 | 1400 |
| 5 | Password123_abc |
15 | 통과 | 1400 | 1400 |
| 6 | Password123~abc |
15 | 통과 | 1400 | 1400 |
| 7 | password12345!a |
15 | 차단 | 1219 | 1220 |
| 8 | Password1가나다 |
12 | 통과 | 1400 | 1400 |
| 9 | Password1 abc (공백) |
13 | 통과 | 1400 | 1400 |
2차 — 축 격리 (password/find)
1차의 4·5·6·8·9 는 허용 8종이 하나도 없는 값이라 「그 문자가 금지」인지 「필수 특수문자 부재」인지 갈리지 않는다. 허용 문자 ! 를 항상 포함시켜 다시 쟀다.
| 축 | 입력 | 결과 |
|---|---|---|
| 기준 | Aa1!aaaa |
1219 (통과) |
| 하이픈 | Aa1!aaa- |
1400 |
| 언더바 | Aa1!aaa_ |
1400 |
| 물결 | Aa1!aaa~ |
1400 |
| 마침표 | Aa1!aaa. |
1400 |
| 한글 | Aa1!aaa가 |
1400 |
| 공백(중간) | Aa1!a a a |
1400 |
| 7자 | Aa1!aaa |
1400 |
| 15자 | Aa1!aaaaaaaaaaa |
1219 (통과) |
| 16자 | Aa1!aaaaaaaaaaaa |
1400 |
| 대문자만 | AA1!AAAA |
1219 (통과) |
| 소문자만 | aa1!aaaa |
1219 (통과) |
@ $ ! % * # ? & 각각 |
Aa1<문자>aaaa 8건 |
8건 모두 1219 (통과) |
이로써 확정된다 — BE 는 8종 외 문자를 전면 금지하고(필수 8종을 이미 갖춘 값도 거절), 길이 상한은 15, 대소문자는 무관, 허용 집합은 정확히 그 8종이다.
auth/password/change 는 같은 상수를 같은 방식으로 물고 있어 게이트가 동일하다. 인증 토큰이 필요하고 유효 입력을 보내면 실제 계정 비밀번호가 바뀌므로 실측하지 않았다.
어긋나는 축
| # | 축 | 내용 | 증상 |
|---|---|---|---|
| 1 | 길이 | FE 16 / BE 15 | 16자가 클라 통과 후 서버 400. 시안 문구가 「8 ~ 16자」라 사용자는 16자를 정당한 입력으로 안내받는다 |
| 2a | 필수 특수문자 | FE 는 영숫자 아닌 아무 문자 1자 / BE 는 8종 중 1자 | FE 만 충족하는 값이 서버 400 |
| 2b | 허용 문자 집합 | FE 는 제한 없음 / BE 는 8종 외 전면 금지 | - _ ~ . 등 흔한 문자가 서버 400 (2차 실측) |
| 3 | 특수문자의 정의 | FE 의 [^A-Za-z0-9] 가 한글·공백까지 특수문자로 인정 |
사용자가 특수문자를 안 넣어도 규칙 충족으로 보이고 서버는 거절 |
| 4 | 대소문자 (역방향) | BE 는 영문 1자면 되는데 FE 는 대문자·소문자 각 1자 요구 | 400 은 안 나지만 규칙이 두 벌. 이 축은 시안 문구(「영문 대소문자」)와 맞는 쪽이 FE 다 |
| 5 | \d 의 런타임 차이 |
Android 는 정규식에 항상 Unicode 문자 클래스를 쓴다 (Pattern.java 의 // Android-changed: Android always uses unicode character classes.) — FE 의 \d 는 아라비아-인도 숫자 등 Unicode 숫자도 숫자로 센다. BE JVM 기본 \d 는 [0-9] |
그런 문자는 FE 에서 숫자 요건과 특수문자 요건을 동시에 만족시키고 서버는 거절. 플랫폼 소스 근거이고 기기 실측은 아직 안 했다 |
축 3·5 는 어느 안을 고르든 FE 를 고쳐야 하는 것이다. 영숫자의 여집합으로 특수문자를 정의하는 방식 자체가 원인이라, 허용 문자를 양수 목록(allowlist)으로 적으면 셋 다 함께 닫힌다.
같은 규칙을 쓰는 화면
| 화면 | 상태 | 규칙 출처 |
|---|---|---|
| 회원가입 3단계 | 구현됨 | SignUpUiState.isPasswordRuleSatisfied → OnboardingPasswordRule |
| 비밀번호 찾기 → 변경 | PR #1624 (열림) | FindPasswordUiState → OnboardingPasswordRule |
| 설정 → 계정 → 비밀번호 변경 | 화면 없음 | — |
앞의 두 화면은 PR #1624 가 OnboardingPasswordRule 하나로 모아 뒀으므로 클라를 고친다면 수정 지점은 한 곳이다.
세 번째는 FE 에 화면이 없다. 설정 메뉴 라벨과 onPasswordChangeClick 콜백만 있고 목적지가 비어 있다(#564 체크리스트). 데이터·도메인 계층(AccountApiService.passwordChange · AccountRepository.passwordChange · AccountRepositoryImpl)은 이미 깔려 있으나 호출부가 0건이라, 화면을 만드는 시점에 같은 규칙을 다시 적을 자리다. 그때 규칙이 확정돼 있어야 한다.
합의가 필요한 것
이 이슈는 어느 쪽에 맞출지를 BE·디자이너와 정하는 것이 본체다. 정해지기 전에는 FE 를 임의로 좁히지 않는다 — 시안 문구가 「8 ~ 16자」라 FE 만 15자로 줄이면 화면 안내와 어긋난다.
| 안 | 내용 | 여파 |
|---|---|---|
| A. 서버를 시안에 맞춘다 | BE 를 8~16자 + 특수문자 범위 확대로 | BE 변경(정규식 · MESSAGE · ErrorCode 1203 문구). FE 는 축 3·5 만 수정. 시안 문구만으로는 구현이 안 된다 — 「특수문자」의 범위, 공백·한글·Unicode 숫자 허용 여부가 문구에 없다 |
| B. 클라·시안을 서버에 맞춘다 | FE 를 8~15자 + 8종으로 | 안내 문구 재작성 필요(디자이너) — 「8 ~ 15자」와 허용 특수문자 노출 여부. FE 는 OnboardingPasswordRule 한 곳 |
| C. 축별로 나눠 맞춘다 | 예: 길이는 서버를 16 으로, 허용 집합은 클라를 allowlist 로 | 두 팀 모두 변경. 축 5개를 각각 정해야 함 |
어느 안이든 정해야 하는 값: ① 길이 상한 ② 허용 문자의 양수 목록 ③ 공백·한글·Unicode 숫자 허용 여부 ④ 대문자·소문자 각각 필수인지 ⑤ 그 결과를 안내 문구에 어디까지 노출할지.
결정에 필요한 사실
- 기존 사용자 중 현행 규칙을 위반하는 비밀번호가 있을 수 있다. BE 는 2026-06-01
c8dde91이전에{8,20}이었다(그 커밋이PasswordValidation을 도입하며 15자로 줄였다). 그 사이 가입한 16~20자 비밀번호가 남아 있을 수 있다. - 그래도 로그인은 안전하다.
LoginRequest.password와PasswordChangeRequest.currentPassword에는@Pattern이 없다. 해시만 저장하므로 기존 비밀번호의 규칙 준수 여부는 판별할 수 없고, 로그인 경로에 새 정규식을 추가하면 안 된다. - 형식 실패의 응답 코드가 선언과 다르다.
ErrorCode.INVALID_PASSWORD_FORMAT(1203)은 선언만 되어 있고 사용처가 0건이다. 실제 응답은1400(위 실측). 규칙을 손볼 때 이 오류 계약과 OpenAPI 예시(password123!)도 함께 정해야 한다. - 길이의 단위를 정해야 한다. Unicode 를 허용하는 안을 고르면 surrogate pair·결합 문자에서 「몇 자」가 갈린다(정규식
.은 code point, 마스킹 표시는 UTF-16 단위). ASCII 로 한정하면 이 축은 사라진다.
완료 조건
- 축 5개(길이 · 필수 특수문자 · 허용 문자 집합 · 특수문자의 정의 · 대소문자)에 대해 FE/BE/시안 셋이 같은 규칙을 가리킨다.
- 합의 결과가 코드에 반영된다 — FE 는
OnboardingPasswordRule, BE 는PasswordValidation.REGEX·MESSAGE·ErrorCode.INVALID_PASSWORD_FORMAT. - 안내 문구(
onboarding_signup_password_rule_combination)가 확정 규칙과 일치한다. - 위 실측표의 케이스가 클라·서버에서 같은 판정을 받는다 — 클라 통과 → 서버 400 인 입력이 0건. 같은 케이스 목록을 FE 단위 테스트와 BE 테스트가 공유하면 재발이 막힌다.
참고
- 이 불일치는 PR #1624 가 「의도적 범위 밖」으로 남기고
OnboardingPasswordRuleKDoc 에 근거를 적어 둔 항목이다. 그 PR 은 기존 동작을 바꾸지 않는다. KDoc 의 「전체 특수문자」 표현은 개행을 제외하므로 정확히는 「영숫자·개행이 아닌 모든 문자」다. - FE 정규식의 출처는 fix: 간편 회원가입 검증·피드백 누락 (인증번호 로딩 / 비밀번호 정규식 / 이름 빈값) #141(2026-05-17 완료) — 안내 문구 「8 ~ 16자」를 그대로 옮긴 것이고, 서버 규칙과의 대조는 그 이슈 범위 밖이었다. 이 이슈는 그 잔존분이다.
- 설정 비밀번호 변경 화면 부재는 tracking(setting): 고객지원·비밀번호 변경 메뉴 목적지 미연결 #564(하위 fix(setting): 비밀번호 변경 메뉴 탭이 무반응 — 목적지 미연결 (#564 분리) #1455). fix(setting): 비밀번호 변경 메뉴 탭이 무반응 — 목적지 미연결 (#564 분리) #1455 는 이슈 메타데이터 가드에 걸려 자동 폐쇄된 상태라 실질 미해결이다.
- 시안: Figma
UP9ZR186jHvRBicjA2SOea— 비밀번호 변경 화면2383:16789. 회원가입 3단계와 안내 2줄이 글자까지 같다. - 이메일 가입 갈래 자체를 걷는 회원가입 카카오 단일화 — 이메일 가입·계정 찾기 갈래 제거 #943(카카오 단일화)이 열려 있으나,
blocked_byAfternote-BE#227 로 무활동이고 기존 이메일 계정 복구 범위는 살아 있다고 스스로 적고 있다. 이 이슈를 대기시킬 근거가 되지 않는다. - 실측은 위 두 표의 케이스를
curl로 반복한 것이다. 재현하려면 dev 서버 개방 시간(12~03시 KST)에 돌린다.
Activity
github-actions commentedon Aug 30, 2026
bug 이슈는 사이드바 Fields > Priority 로 심각도를 지정해야 합니다. 판정 기준:
값이 비어 있어도 이슈를 닫지는 않습니다. 이 안내는 이슈당 한 번만 답니다.