
들어가며..
안녕하세요! 요즘 서버 측 인증 방식이 궁금해서 공부해보았습니다.
JWT와 세션(Session)은 많이 사용되는 인증 방식으로, 각각이 어떻게 작동하는지 그리고 iOS 개발과 어떻게 연결되는지 알아보겠습니다. 처음에는 단순히 어떤 방식이 더 좋은지 알고 싶었지만, 공부하면서 인증이 처리되는 흐름을 이해하는 것이 개발 과정에서 훨씬 더 중요하다는 것을 느꼈습니다.
이 글에서는 JWT와 세션의 차이점을 정리하고, 각각의 특징을 같이 알아보아요 :)
JWT(JSON Web Token)
JSON 객체를 이용해 정보를 안전하게 표현하는 방식으로, 일반적으로 인증(Authentication)과 정보 교환을 목적으로 사용됩니다.
특히, 무상태(Stateless) 방식으로 동작하기 때문에 서버가 클라이언트의 인증 상태를 직접 관리하지 않아도 된다는 장점이 있습니다.
JWT의 구성 요소
JWT는 세 가지 구성 요소를 점(.)으로 구분하여 표현합니다.
- Header (헤더)
- Payload (페이로드)
- Signature (서명)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvbiBEb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
이처럼 세 가지를 조합하여 하나의 JWT가 생성됩니다.
이 JWT는 보통 개발할때 Bearer Token에 들어가는 값이에요!!
API 요청 시, 아래와 같은 형식으로 Authorization 헤더에 포함해야 합니다.
자세한 내용은 하단에서 다시 다룰 예정이니 참고해주세요!
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvbiBEb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
1. Header (헤더)
- JWT의 유형과 해싱 알고리즘 정보를 담고 있음
- 일반적으로 다음과 같은 JSON 객체 형식
{
"alg": "HS256", // 해싱 알고리즘 (예: HS256, RS256)
"typ": "JWT" // 토큰의 유형
}
2. Payload (페이로드)
- 실제 데이터가 포함된 부분
- 사용자 ID, 역할, 권한, 만료 시간과 같은 Claim이 포함됨
{
"sub": "1234567890", // 사용자 ID
"name": "Sojin", // 사용자 이름
"role": "admin", // 사용자 역할
"iat": 1516239022, // 발행 시각
"exp": 1516242622 // 만료 시각
}
⚠️ 주의: JWT의 페이로드는 암호화되지 않기 때문에, 민감한 정보를 저장하면 안 됩니다. ⚠️
3. Signature (서명)
- 헤더와 페이로드를 조합한 후, 비밀 키(secret key)를 사용하여 생성한 서명
- 토큰의 무결성을 보장
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload), secret
)
JWT의 동작 원리

- 로그인 요청
- 클라이언트(웹, 모바일 등)는 사용자 이름과 비밀번호로 서버에 로그인 요청을 보냄
- JWT 생성 및 반환
- 서버는 인증 성공 시 JWT를 생성하고 클라이언트에 반환
- 이 JWT는 이후 요청마다 인증을 증명하는 데 사용 함
- JWT 전달 및 API 요청
- 클라이언트는 API 요청을 보낼 때, JWT를 HTTP 헤더 (Authorization: Bearer <JWT>)에 포함하여 전송
- JWT 검증 및 요청 처리
- 서버는 클라이언트로부터 받은 JWT를 해독하고 서명을 확인하여 변조되지 않았음을 검증
- 검증이 통과되면, JWT의 페이로드(Payload)에 있는 사용자 정보를 바탕으로 요청을 처리 함
JWT의 장점
- Stateless (상태 비저장)
- 클라이언트가 JWT를 가지고 있기 때문에 서버가 상태를 유지할 필요가 없음
- 서버 간 확장성이 뛰어나며, 세션 관리가 필요 없음
- 유연한 사용
- 도메인 간 인증(CORS)과 같은 다양한 상황에서 사용 가능
- 웹, 모바일, 서버 간 통신 모두 지원
- Self-contained (자급자족 구조)
- 필요한 정보를 페이로드에 포함하여 데이터베이스 조회 없이 요청을 처리 가능
- 보안
- 서명을 통해 변조를 방지
- 만료 시간(exp)을 설정하여 토큰의 유효성을 제어 가능
JWT의 단점
- 토큰 크기 증가
- 헤더, 페이로드, 서명을 포함하기 때문에 크기가 커질 수 있음
- 요청 크기가 커지면서 트래픽 비용 증가 가능
- 서버에서 해독 필요
- 단순 서명된 경우, 암호화되지 않으면 악의적인 사용자가 토큰 내용을 볼 수 있음
- 만료된 토큰 관리 어려움
- JWT는 서버가 상태를 유지하지 않으므로, 특정 토큰을 무효화하기 어려움
- 보안 사고 발생 시, 기존 모든 토큰을 폐기해야 함
- 보안 위험
- 키 관리 실패, 토큰 노출, 만료 시간 설정 오류는 보안 취약점을 초래할 수 있음
JWT 토큰 단점 보안 방법: Refresh Token 활용
JWT의 무상태(Stateless) 특성 때문에, 특정 사용자의 토큰을 강제로 만료시키기 어렵다는 문제가 있다고했었죠?
이 문제는 Refresh Token을 활용하면 보안성을 높일 수 있습니다.
✅ Access Token: 유효 기간이 짧음 (예: 1530분), API 요청마다 전송✅ Refresh Token: 유효 기간이 김 (예: 730일), Access Token이 만료되면 새 토큰을 요청하는 데 사용
그러면 이제 iOS 개발할때는 어떻게 사용되는지 한 번 살펴볼까요?
1. Refresh Token 발급 및 저장
사용자가 로그인하면 Access Token + Refresh Token을 함께 발급합니다.
Access Token이나 Refresh Token이 탈취될 경우 공격자가 사용자의 인증 정보를 악용할 수 있기 때문에
iOS에서는 보안성이 높은 Keychain을 사용하여 JWT를 저장하는 것이 권장됩니다.
Keychain은 Apple이 제공하는 보안 저장소로, 민감한 데이터를 안전하게 보관할 수 있습니다.
UserDefaults에 저장하는 경우 (비추천)
UserDefaults.standard.set(jwtToken, forKey: "accessToken")
- UserDefaults는 암호화되지 않은 데이터를 저장하므로, 탈옥된 기기에서 쉽게 탈취될 가능성이 높음
- 앱이 삭제되거나 재설치될 경우 데이터가 사라짐
Keychain에 저장하는 경우 (추천)
let keychain = Keychain(service: "com.example.app")
try? keychain.set(jwtToken, key: "accessToken")
- Keychain은 강력한 암호화를 제공하며, 앱 간 데이터 공유 및 저장된 데이터 보호 기능을 지원
- 앱이 삭제되더라도 Keychain에 저장된 데이터는 유지됨 (옵션에 따라 달라질 수 있음)
- Face ID, Touch ID를 활용하여 추가 보안 적용 가능
2. JWT 갱신 로직 (Refresh Token 관리)
Access Token이 만료되면, Refresh Token을 이용하여 새로운 Access Token을 발급해야 합니다.
하지만 Refresh Token도 만료되거나 서버에서 무효화될 수 있으므로 이에 대한 처리가 필요합니다.
func refreshAccessToken(completion: @escaping (String?) -> Void) {
guard let refreshToken = KeychainHelper.shared.retrieve(forKey: "refreshToken") else {
print("Refresh Token 없음. 로그아웃 필요")
completion(nil)
return
}
var request = URLRequest(url: URL(string: "https://-.com/-")!)
request.httpMethod = "POST"
request.setValue("Bearer \(refreshToken)", forHTTPHeaderField: "Authorization")
Session (세션)
세션은 서버가 클라이언트의 상태를 유지하는 방식으로, 사용자가 로그인한 이후 클라이언트와 연결된 정보를 서버 측에서 저장합니다.
세션의 특징
- 서버 측 저장
- 클라이언트의 정보는 서버에 저장됨
- 클라이언트는 세션 ID만 저장하며, 요청 시 이를 서버로 전달
- 클라이언트-서버 연결 유지
- 클라이언트가 요청을 보낼 때 세션 ID를 포함하면 서버가 해당 데이터를 찾아 클라이언트 상태 유지
- 수명 관리
- 세션은 일정 시간 동안 유효하며, 일정 시간 동안 활동이 없으면 만료됨 (타임아웃)
- 보안성
- 클라이언트는 세션 데이터를 직접 볼 수 없음 → 데이터 노출 위험 낮음
- 그러나 세션 ID가 탈취되면 보안 문제가 발생할 수 있으므로 HTTPS 사용 필수
세션의 동작 원리

- 클라이언트 요청
- 클라이언트가 서버에 로그인 요청을 보냄
- 세션 ID 생성
- 서버는 사용자의 인증이 성공하면 세션 ID를 생성
- 세션 ID 전달
- 서버는 생성한 세션 ID를 쿠키에 저장하여 클라이언트에 반환
- 세션 데이터 저장
- 서버는 세션 ID와 함께 관련 데이터를 저장
- 일반적으로 메모리, 데이터베이스, 또는 파일 시스템에 저장됨
- 상태 유지(API 요청 시 세션 ID 포함)
- 클라이언트는 이후 API 요청을 보낼 때, 세션 ID를 포함하여 서버에 요청 전송
- 웹에서는 쿠키를 통해 자동으로 전송됨
- 서버의 세션 조회
- 서버는 요청에 포함된 세션 ID를 기반으로 저장된 세션 데이터를 조회하여 클라이언트 상태 유지
🤔 세션과 쿠키 뭐가 다른거야?
구분 | 세션(Session) | 쿠키(Cookie) |
저장 위치 | 서버 | 클라이언트(브라우저) |
보안성 | 안전함 (클라이언트에 데이터 저장 안 함) | 브라우저에 저장되므로 노출 위험 있음 |
속도 | 서버에 저장되므로 상대적으로 느림 | 클라이언트에서 처리되어 상대적으로 빠름 |
데이터 용량 | 서버 용량에 따라 결정 | 브라우저당 약 4KB 제한 |
사용 목적 | 로그인 상태, 사용자 정보 유지 | 비로그인 사용자 설정, 언어, 최근 방문 기록 |
iOS에서 세션과 JWT 기반 인증이 어떻게 적용되는거야?
세션 기반 인증 (Session-based Auth)
- 쿠키 기반 인증이므로 기본적으로 iOS의 URLSession이나 WKWebView와 함께 사용됨
- iOS 앱에서는 HTTPCookieStorage를 활용하여 쿠키를 관리할 수 있음
- 하지만 iOS의 네이티브 네트워크 요청 (URLSession)은 자동으로 쿠키를 저장하지 않음
→ HTTPCookieStorage.shared.setCookie(_:) 등을 사용하여 직접 저장해야 함
JWT 기반 인증 (Token-based Auth)
- JWT는 쿠키를 사용하지 않기 때문에, 클라이언트가 Authorization 헤더에 JWT를 추가해서 직접 전송해야 함
- URLSession을 사용할 때, URLRequest의 setValue(_:forHTTPHeaderField:) 메서드로 JWT를 헤더에 추가 가능
var request = URLRequest(url: URL(string: "https://api.example.com/user")!)
request.setValue("Bearer \(jwtToken)", forHTTPHeaderField: "Authorization")
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
// 응답 처리
}
task.resume()
세션과 JWT를 iOS 앱에서 사용할 때 고려해야 할 점
1. 세션을 사용할 때 주의할 점
- 세션은 쿠키를 활용하므로, iOS 앱이 쿠키를 제대로 저장하고 전송하는지 확인해야 함
- WKWebView를 사용할 경우, 자동으로 쿠키를 관리하지만 URLSession은 쿠키를 자동으로 저장하지 않음
- HTTPCookieStorage.shared를 활용하여 쿠키를 직접 저장 및 전송해야 함
- 앱이 백그라운드로 전환되거나 종료될 경우, 세션이 만료될 수 있음 → 적절한 재로그인 처리 필요
2. JWT를 사용할 때 주의할 점
- JWT는 클라이언트가 직접 저장해야 하므로, 보안 처리가 중요함
- JWT를 UserDefaults에 저장하면 보안 취약점이 발생할 수 있음 → Keychain 사용 추천
- iOS에서 Keychain Services를 활용하여 JWT를 안전하게 저장 가능
let jwtToken = "your_jwt_token_here"
let keychain = Keychain(service: "com.example.app")
try? keychain.set(jwtToken, key: "jwtToken")
- JWT는 무상태(Stateless) 방식이므로, 서버가 강제로 만료시키기 어렵다
- iOS에서는 JWT의 exp(만료 시간)를 확인하여 자동 로그아웃 처리를 해야 함
- exp가 지나면 새로운 JWT를 요청하도록 구현
JWT과 세션 요약
비교 항목 | 세션(Session) | JWT(Token) |
저장 위치 | 서버 (세션 데이터 유지) | 클라이언트 (JWT 직접 저장) |
보안성 | 서버에서 관리하므로 상대적으로 안전 | 클라이언트에서 직접 관리해야 하므로 보안 위험 |
네트워크 요청 방식 | 쿠키를 이용하여 자동 전송 (HTTPCookieStorage) | Authorization 헤더에 직접 추가 |
만료 및 갱신 | 세션 만료 후 다시 로그인 필요 | JWT는 만료 시간(exp)을 확인하고 갱신해야 함 (Refresh Token 활용 가능) |
확장성 | 서버가 상태를 유지해야 하므로 확장성 낮음 | 서버가 상태를 유지할 필요 없으므로 확장성 높음 |
iOS에서의 사용성 | WKWebView에서 쿠키 자동 관리 가능 (URLSession에서는 직접 처리 필요) | URLSession 사용 시 Authorization 헤더에 직접 추가해야 함 |
iOS 앱에서 세션과 JWT를 함께 사용할 수도 있다?
- 대부분의 iOS 앱에서는 JWT를 주 인증 방식으로 사용하지만, 세션을 함께 활용하여 보안을 강화할 수도 있음.
- 예를 들면:
- JWT + Refresh Token 패턴을 사용하여, JWT가 만료되면 세션을 기반으로 새 JWT를 발급
- 서버에서 JWT 발급 시, 세션도 함께 생성하여 백엔드에서 추가적인 보안 체크 수행
iOS 개발자는 어떤 인증 방식을 선택해야 할까?
웹 기반 서비스 (iOS + 웹 연동이 필요한 경우)
- 세션 기반 인증이 유리
- 쿠키를 자동으로 활용 가능 (WKWebView 사용 시)
- 기존 웹 서비스와 동일한 방식으로 로그인 유지 가능
REST API 기반 서비스 (iOS 네이티브 앱 중심일 경우)
- JWT 기반 인증이 유리
- Authorization 헤더를 사용하여 API 요청마다 JWT 전송
- 서버 상태를 유지하지 않으므로 확장성 높은 인증 구조 가능
하이브리드 방식 (JWT + 세션 병행 사용 가능)
- JWT를 기본 인증 방식으로 사용하면서, Refresh Token을 세션에 저장하여 추가적인 보안 체크 수행
- 인증 과정:
- 로그인 시 Access Token + Refresh Token 발급
- Access Token이 만료되면 Refresh Token을 이용해 재발급 요청
- 서버에서 Refresh Token이 유효한지 세션을 통해 한 번 더 검증
마무리
JWT와 세션은 각각 장점과 단점이 있기 때문에, 특정 상황에 따라 더 적절한 선택지가 될 수 있습니다.
예를 들어, 모바일 앱과 같이 Stateless한 환경에서는 JWT가 더 적합할 수 있지만, 반대로 웹 서비스에서 사용자 정보를 서버에서 강하게 관리해야 하는 경우에는 세션 기반 인증이 더 유리할 수 있습니다.
제가 프로젝트를 진행하며 직접 인증 방식을 고민했던 경험을 돌아보면, JWT가 편리하다고 무조건 사용하는 것이 아니라, 보안과 관리 측면을 충분히 고려해야 한다는 점을 깨달았습니다.
이 글이 여러분이 프로젝트 상황에 맞는 인증 방식을 선택하는 데 조금이나마 도움이 되었으면 좋겠습니다.
혹시 틀린 부분이나 보완할 점이 있다면 댓글로 의견 남겨주세요! 바로 반영하겠습니다.
긴 글 읽어주셔서 감사합니다! 🙇🏻♀️