1단계: 비밀번호 해시와 JWT 로그인
5. 자주 하는 실수 (Tip)
❌ 실수 1: 헤더의 alg 를 믿는다
String alg = header.get("alg");
if (alg.equals("none")) return payload; // ❌ 서명 생략. 2015년 실제 취약점
if (alg.equals("HS256")) verifyHmac(...); else verifyRsa(...); // ❌ 키 혼동: 공개키를 HMAC 키로 쓴 토큰이 통과
requireAlg(t, "HS256"); // ✅ 기대 알고리즘을 코드에 고정❌ 실수 2: 짧거나 고정된 비밀키
byte[] secret = "mysecret".getBytes(); // ❌ 64비트. 사전 공격 수초. jjwt 는 WeakKeyException 을 던진다
byte[] secret = new byte[32]; new SecureRandom().nextBytes(secret); // ✅ 256비트 난수. 환경변수/비밀 저장소에서 주입키를 코드나 application.yml 에 적어 git 에 올리면 저장소 접근 권한이 곧 토큰 위조 권한입니다.
❌ 실수 3: 페이로드에 비밀을 넣는다
Jwt.claims(…, Map.of("password", pw)) // ❌ Base64Url 은 암호화가 아니다. 브라우저 개발자 도구에서 그대로 보인다암호화가 필요하면 JWE(암호화된 JWT)를 쓰지만, 대부분은 "토큰에 넣지 않는다"가 정답입니다.
❌ 실수 4: "존재하지 않는 아이디입니다"
if (user == null) return 404 "존재하지 않는 아이디"; // ❌ 아이디 목록 수집 가능
if (!verify(...)) return 401 "비밀번호 오류";
return 401 "아이디 또는 비밀번호가 올바르지 않습니다"; // ✅ 둘 다 같은 응답 + 더미 해시로 시간도 같게❌ 실수 5: 상수 시간 비교를 안 한다
Arrays.equals(expected, actual) // ❌ 첫 다른 바이트에서 반환. 응답 시간으로 한 바이트씩 맞출 수 있다
MessageDigest.isEqual(expected, actual) // ✅ 길이가 같으면 끝까지 비교네트워크 지터 때문에 실제 공격은 어렵지만, 비용이 0 인 방어를 빼먹을 이유가 없습니다.
❌ 실수 6: HTTP 헤더에 한글을 넣는다
headers.add("WWW-Authenticate", "Bearer error_description=\"서명 불일치\""); // ❌ 클라이언트가 응답 자체를 거부 (Invalid header value)
headers.add("WWW-Authenticate", "Bearer error=\"invalid_token\""); // ✅ 헤더는 ASCII 코드만. 사유는 JSON 본문에이 레슨 작성 중 실제로 났던 오류입니다. Content-Disposition 의 한글 파일명(실무 확장 03 파일 업로드 레슨)과 같은 계열의 문제입니다.
❌ 실수 7: 삼항식으로 숫자 타입을 섞는다
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 모두 같은 규칙으로 승격됩니다.