홈 › 인증·로그인 › 01 / 3

1단계: 비밀번호 해시와 JWT 로그인

섹션 7진행 0 / 3

5. 자주 하는 실수 (Tip)

❌ 실수 1: 헤더의 alg 를 믿는다

java
String alg = header.get("alg");
if (alg.equals("none")) return payload;                      // ❌ 서명 생략. 2015년 실제 취약점
if (alg.equals("HS256")) verifyHmac(...); else verifyRsa(...); // ❌ 키 혼동: 공개키를 HMAC 키로 쓴 토큰이 통과
requireAlg(t, "HS256");                                      // ✅ 기대 알고리즘을 코드에 고정

❌ 실수 2: 짧거나 고정된 비밀키

java
byte[] secret = "mysecret".getBytes();                       // ❌ 64비트. 사전 공격 수초. jjwt 는 WeakKeyException 을 던진다
byte[] secret = new byte[32]; new SecureRandom().nextBytes(secret);   // ✅ 256비트 난수. 환경변수/비밀 저장소에서 주입

키를 코드나 application.yml 에 적어 git 에 올리면 저장소 접근 권한이 곧 토큰 위조 권한입니다.

❌ 실수 3: 페이로드에 비밀을 넣는다

java
Jwt.claims(…, Map.of("password", pw))                        // ❌ Base64Url 은 암호화가 아니다. 브라우저 개발자 도구에서 그대로 보인다

암호화가 필요하면 JWE(암호화된 JWT)를 쓰지만, 대부분은 "토큰에 넣지 않는다"가 정답입니다.

❌ 실수 4: "존재하지 않는 아이디입니다"

java
if (user == null) return 404 "존재하지 않는 아이디";           // ❌ 아이디 목록 수집 가능
if (!verify(...)) return 401 "비밀번호 오류";
return 401 "아이디 또는 비밀번호가 올바르지 않습니다";           // ✅ 둘 다 같은 응답 + 더미 해시로 시간도 같게

❌ 실수 5: 상수 시간 비교를 안 한다

java
Arrays.equals(expected, actual)                               // ❌ 첫 다른 바이트에서 반환. 응답 시간으로 한 바이트씩 맞출 수 있다
MessageDigest.isEqual(expected, actual)                       // ✅ 길이가 같으면 끝까지 비교

네트워크 지터 때문에 실제 공격은 어렵지만, 비용이 0 인 방어를 빼먹을 이유가 없습니다.

❌ 실수 6: HTTP 헤더에 한글을 넣는다

java
headers.add("WWW-Authenticate", "Bearer error_description=\"서명 불일치\"");   // ❌ 클라이언트가 응답 자체를 거부 (Invalid header value)
headers.add("WWW-Authenticate", "Bearer error=\"invalid_token\"");            // ✅ 헤더는 ASCII 코드만. 사유는 JSON 본문에

이 레슨 작성 중 실제로 났던 오류입니다. Content-Disposition 의 한글 파일명(실무 확장 03 파일 업로드 레슨)과 같은 계열의 문제입니다.

❌ 실수 7: 삼항식으로 숫자 타입을 섞는다

java
return hasDot ? Double.parseDouble(n) : Long.parseLong(n);   // ❌ 이항 수치 승격: 전체가 double → 정수 1788927349 가 1.788927349E9 로
if (hasDot) return Double.parseDouble(n);                    // ✅ 분기로 나눈다
return Long.parseLong(n);

JWT 와 무관한 자바 함정이지만 이 레슨의 JSON 파서에서 실제로 났습니다. exp 가 1.78E9 로 찍히는 것을 보고 찾았습니다. cond ? int : long, cond ? Integer : Double 모두 같은 규칙으로 승격됩니다.