에듀테크 프로젝트 대시보드
JWT (JSON Web Token)
로그인에 성공하면 서버는 "이 사람은 확인된 사용자"라는 출입증(토큰)을 발급한다.
JWT는 그 출입증을 헤더.페이로드.서명 세 조각을 점(.)으로 이은 문자열로 표현한다.
내용은 암호화되지 않고 그냥 Base64URL로 인코딩만 되어 누구나 읽을 수 있지만,
맨 뒤의 서명 때문에 내용을 위조하면 즉시 들통난다. 아래에서 직접 만들고 깨뜨려 보자.
토큰 문자열을 그대로 복사해 jwt.io 같은 사이트에 붙여넣으면 페이로드가 그대로 보인다. 즉 JWT에 비밀번호·주민번호 같은 민감정보를 넣으면 안 된다. 서명은 "내용 보호"가 아니라 "위조 방지"다.
role 을 student → admin 으로 바꿔 보자
위 PAYLOAD의 "role": "student" 를 "admin" 으로 고쳐 보자.
토큰 문자열은 바뀌지만, 비밀키를 모르면 서명을 다시 만들 수 없다.
아래 검증기는 서버가 하는 일을 흉내 낸다 — 받은 토큰의 앞 두 조각으로 서명을 재계산해서
토큰에 붙어온 서명과 일치하는지만 본다.
# 1) 헤더·페이로드를 각각 JSON → Base64URL 인코딩 h = base64url(json(header)) p = base64url(json(payload)) # 2) 두 조각을 점으로 이은 뒤, 비밀키로 HMAC-SHA256 signing_input = h + "." + p sig = base64url( HMAC_SHA256(key=secret, msg=signing_input) ) # 3) 최종 토큰 = 세 조각을 점으로 연결 token = signing_input + "." + sig # 검증(서버): 같은 방식으로 sig 를 다시 계산해서 비교 assert HMAC_SHA256(secret, h + "." + p) == b64url_decode(sig)
HMAC(Hash-based Message Authentication Code)은 "메시지 + 비밀키"를 함께 해시해서 만든 짧은 지문이다. 비밀키가 1글자만 달라도 결과가 완전히 달라지고, 지문만 보고 비밀키를 역산할 수 없다. 그래서 비밀키를 가진 서버만 올바른 서명을 만들 수 있다.
alg: "none" 공격: 옛날 라이브러리 중엔 헤더의 alg 를 none 으로 바꾸면
서명 검증을 통째로 건너뛰는 버그가 있었다. 그래서 서버는 허용할 알고리즘을 코드에 고정하고,
클라이언트가 보낸 alg 값을 그대로 믿으면 안 된다.
로그인 요청 — 브라우저가 아이디·비밀번호를 서버로 보낸다.
토큰 발급 — 서버가 아이디를 확인하고, 사용자 정보 + exp(만료)를 담아 서명한 JWT를 돌려준다.
저장 — 브라우저가 토큰을 보관한다(쿠키 또는 메모리). 이후 요청마다 Authorization: Bearer <토큰> 헤더로 함께 보낸다.
검증 — 서버는 매 요청마다 서명·만료만 확인한다. DB 조회 불필요 — 토큰 자체가 신원 증명이라 서버가 세션을 기억하지 않아도 된다(무상태·stateless).
만료 — exp 가 지나면 서버가 거부한다. 보통 짧게(15분) 두고, 긴 refresh token 으로 새로 발급받는다.
| 서버 세션 (쿠키+세션ID) | JWT | |
|---|---|---|
| 서버가 기억하는 것 | 세션 저장소에 로그인 상태 전부 저장 | 비밀키 하나만. 상태는 토큰 안에 |
| 요청마다 | 세션ID로 저장소 조회 | 서명·만료만 계산 (조회 없음) |
| 서버 여러 대로 확장 | 세션 공유 저장소(Redis 등) 필요 | 비밀키만 공유하면 끝 유리 |
| 즉시 로그아웃/차단 | 세션 지우면 끝 쉬움 | 만료 전엔 강제 무효화가 어려움 약점 |
| 토큰 크기 | 세션ID 짧음 | 페이로드만큼 커짐(매 요청 전송) |
정답은 없다. 모바일 앱·여러 서버·다른 회사 API 연동엔 JWT가, 단일 웹서비스로 즉시 차단이 중요하면 세션이 편하다.
이 학교 서버의 fastapi-lms 는 세션 방식, 많은 오픈 API는 JWT(또는 그 친척인 OAuth 액세스 토큰)를 쓴다.
| 필드 | 이름 | 뜻 |
|---|---|---|
sub | subject | 토큰의 주인 (보통 사용자 ID) |
iat | issued at | 발급 시각 (Unix time, 초) |
exp | expiration | 만료 시각. 이 시각이 지나면 무효 |
nbf | not before | 이 시각 전에는 사용 불가 |
iss | issuer | 토큰을 발급한 주체 (서비스 이름) |
aud | audience | 이 토큰을 받아야 할 대상 |
jti | JWT ID | 토큰 고유 번호 (재사용/블랙리스트용) |
algorithms=["HS256"])하고 alg: none 을 거부한다.exp)를 짧게. 탈취돼도 피해 시간이 줄어든다. 긴 세션은 refresh token으로.HttpOnly 쿠키 + SameSite 가 더 안전.
서명 계산은 브라우저 Web Crypto API(crypto.subtle)로 이 페이지 안에서만 이뤄지며, 입력값은 서버로 전송되지 않습니다.