Skip to content

fix(onboarding): 비밀번호 규칙이 서버보다 느슨해 클라 통과분이 서버 400 — 길이·특수문자 등 4축 불일치 #1628

Closed

Description

@OnlyReadOneBook

작업 유형

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 테스트가 공유하면 재발이 막힌다.

참고

Activity

  1. github-actions commented on Aug 30, 2026

    @github-actions
    Contributor

    bug 이슈는 사이드바 Fields > Priority 로 심각도를 지정해야 합니다. 판정 기준:

    • Urgent: 치명 — 크래시·데이터 소실·보안 무력화·핵심 경로 차단·가짜 성공
    • High: 심각 — 실패·틀린 값의 정상 위장, 핵심 기능 무동작
    • Medium: 불편 — 우회로가 있는 오동작·틀린 표시, 핵심 흐름은 유지
    • Low: 경미 — 문구·정렬·마감 품질 흠, 기능 영향 없음

    값이 비어 있어도 이슈를 닫지는 않습니다. 이 안내는 이슈당 한 번만 답니다.

  2. added
    pr-open이 이슈를 구현하는 PR 이 열려 있다 — base 가 기본 브랜치가 아니어도 드러낸다
    on Aug 30, 2026
  3. removed
    pr-open이 이슈를 구현하는 PR 이 열려 있다 — base 가 기본 브랜치가 아니어도 드러낸다
    on Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:onboarding온보딩 기능 영역bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions