⎮ JWT는 위험하다?
JWT를 공부하다보면, "JWT는 보안상 취약한 부분이 있다"라던지,
"Payload가 평문화되어 있어 주의해서 사용해야 한다"는 말을 많이 듣게 됩니다
이 때문에 'JWT 자체가 위험한거 아닌가'하고 생각할 수 있는데요
그런데 애플은 로그인에 JWT를 사용합니다
문득 왜 애플이 JWT를 사용하는건지 궁금해졌습니다
먼저 JWT를 알아보고,
그 뒤 애플이 어떻게 이를 안전하게 사용하고 있는지 알아보겠습니다
⎮ 인증(Authentication) vs 인가(Authorization)
JWT를 이해하기 전에 인증과 인가에 대해 짚고 넘어가보겠습니다
인증은 사용자의 신원을 확인하는 것(예를 들면 로그인)이고,
인가는 사용자의 권한을 판단하는게 목적(예를 들면 관리자 페이지, 파일 수정 권한 등)입니다
다시 말하면 인증은 "너 누군데?"하는거고, 인가는 "너 이거 해도 돼"라고 설명할 수 있습니다
⎮ 라떼는 말이야...(feat. 세션)
웹 서비스 초기에는 세션(Session) 방식으로 인증과 인가를 처리해왔습니다
세션 기반 인증 흐름을 간략하게 살펴보면,
1. 사용자_로그인 -> 서버_아이디/비밀번호 확인
2. 로그인 성공 : 세션 ID 생성, 서버 메모리에 저장
3. 세션 ID를 쿠키에 담아 클라이언트로 전송
4. 이후 발생하는 요청마다 클라이언트가 쿠키를 자동으로 보냄
5. 서버가 세션 ID로 사용자 식별
세션 기반 인증은 위와 같은 플로우로 진행되다보니,
서버가 모든 세션을 기억해야한다는 단점이 생겼습니다
또한 모바일 앱이나 API 클라이언트는 쿠키 사용이 불편했죠
그래서 등장하게 된 것이 바로 JWT입니다
⎮ JWT란 (Json Web Token)
JWT란, 속성 정보(Claim)을 JSON 데이터 구조로 표현한 토큰입니다
네트워크 통신 간 안전한 전송을 위해서 토큰을 주는 것이 바로 JWT입니다
JWT는 총 세 파트(헤더/페이로드/서명)로 나뉘어지고, 각 파트는 점으로 구분하여 표현합니다
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0
.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30
⎮ JWT 헤더
먼저 헤더는 해시 암호화 알고리즘과 토큰의 타입으로 구성되어 있습니다
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
{
"alg": "HS256",
"typ": "JWT"
}
- alg : 서명 생성에 사용될 *해시 알고리즘(HMAC, SHA256 or RSA)
- typ : 토큰의 유형(JWT_고정값)
⎮ JWT 페이로드
페이로드에는 토큰에 담을 *클레임(Claim) 정보, 즉 내용이 담깁니다
{
"iss": "https://auth.example.com", // registered
"sub": "user-1234", // registered
"aud": "https://api.example.com", // registered
"exp": 1718012345, // registered (epoch)
"iat": 1718008745, // registered
"role": "admin", // private claim
"https://example.com/claims/locale": "ko" // public-style claim (네임스페이스)
}
위처럼 토큰에는 여러 개의 클레임을 넣을 수 있습니다
- iss : 토큰 발급자
- sub : 토큰 제목
- aud : 토큰 대상자
- exp : 토큰 만료 시간
- nbf : 토큰 활성 날짜
- iat : 토큰 발급 시간
- jti : JWT 토큰 식별자
등으로 구성됩니다
⎮ JWT 서명
서명 항목에는 Header, Payload, Secret Key를 합쳐서 서명한 값이 들어갑니다
b'\xcfxs\xc5\xe5!\xcc\xe4\x1b\x86\xe3~\xf7\x08\x15...'
헤더와 페이로드는 디코딩하면 내용을 확인할 수 있지만,
서명은 Secret Key 없이는 검증만 가능하고 원본 데이터로 복원할 수 없습니다
⎮ JWT를 사용한 Login Flow
사용자가 로그인을 시도합니다
서버에서 요청을 확인하고, Secret Key와 Payload를 조립해서 서명을 만듭니다
이게 바로 JWT입니다
서버에서는 JWT 토큰을 클라이언트에게 전달합니다
클라이언트에서 API를 요청할 때 클라이언트가 인증 헤더에 토큰을 담아서 전송합니다
서버는 JWT Signature를 체크하고, Payload로부터 사용자 정보를 확인해서 데이터를 반환하게 됩니다
⎮ JWT 주의사항
1. JWT는 서명된 토큰이지 암호화된 토큰이 아닙니다
Payload 자체가 평문(base64Url 인코딩)이므로 누구나 내용을 볼 수 있습니다
JSON Web Tokens - jwt.io
JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is digitally signed using JSON Web Signature (JWS).
www.jwt.io
2. 헤더의 알고리즘을 선택할 때 none의 사용을 피해야 합니다
서명 검증이 우회될 가능성이 있습니다
3. 토큰 검증을 잘 해야 합니다
- 올바른 키와 알고리즘으로 서명되었는지
- 만료 시간이 지나지는 않았는지
- 사용 가능 시간이 맞는지
- 발급 시간이 적절한지
- 토큰 대상이 일치하는지
- 클레임 데이터 타입 / 형식이 일치하는지
4. 액세스 토큰의 만료시간은 짧게 설정합니다
5. 리프레시 토큰 전략을 사용합니다
6. 키는 안전하게 교환하고, 키 교체 전략을 사용합니다
⎮ 애플은 왜 로그인에 JWT를 사용할까?
먼저, 애플의 로그인은 OIDC 방식입니다
이 OIDC 방식의 핵심이 바로 JWT인데요
애플은 이 JWT를 단순히 "로그인 상태를 저장"하려는 목적으로 사용하는 것이 아니라,
Apple이 보낸 '인증 정보가 진짜'임을 증명하기 위한 수단으로 사용합니다
그렇다면 애플은 왜 JWT를 사용하는 걸까요?
먼저, 표준화와 호환성 때문입니다
OIDC 자체가 표준화된 인증 방식입니다(Google, Facebook, Github 모두 JWT 사용)
이렇다보니 다른 플랫폼과도 쉽게 연동이 가능합니다
다음으로, 보안성 때문입니다
Apple id_token은 RS256으로 서명되어 있고,
client_secret은 ES256으로 서명되어 있습니다
Apple이 개인키(private key)로 서명하고,
Apple이 제공하는 공개키(public key)로 검증이 가능합니다
따라서 변조가 불가능합니다
마지막으로, 확장성 때문입니다
Apple은 많은 클라이언트에게 로그인 기능을 제공하기 때문에
세션 방식을 사용했을 경우 오버헤드가 발생합니다
JWT는 Stateless(무상태성)하기 때문에 상태를 서버에 저장하지 않아도 됩니다
⎮ 애플이 안전하게 JWT를 사용하는 방법
우선 Apple의 id_token에는 민감한 정보가 없습니다
id_token에 이메일이 포함되긴 하지만,
심지어 그마저도 privaterelay.appleid.com 와 같은 형태로 감춰져있습니다
HS256과 같은 대칭키 알고리즘은 키가 유출되었을 때 위험할 수 있습니다
따라서 Apple은 RS256(비대칭키)를 사용하여 개인키는 Apple만 알고,
공개키를 사용해서 검증을 하도록 합니다
https://appleid.apple.com/auth/keys
Apple은 exp(만료시간)을 매우 짧게 설정해두었습니다
따라서 토큰의 재사용이 방지되며 authorization_code 교환 시점에
서버 검증을 한번 더 수행하는 로직으로 안전하게 JWT를 사용하고 있습니다
⎮ 애플은 얼마나 토큰을 보관할까?
애플이 얼마나 토큰을 보관하는지는 다음에 실험해보고 이어서 포스팅해보겠습니다
'Swift > TOPIC' 카테고리의 다른 글
| Swift | final 키워드에 대한 고찰(feat. Method Dispatch) (0) | 2025.11.12 |
|---|---|
| Swift | 네트워크 예외처리를 해보자(2) - 상태 코드 / 서버 응답 (0) | 2025.09.21 |
| Swift | 네트워크 예외처리를 해보자(1) (0) | 2025.09.16 |
| Hash / Hashable / Hasher / HashTable (0) | 2025.09.09 |
| Swift | 메모리 누수(2) (Memory Graph) (0) | 2025.09.01 |