토큰, 세션, 쿠키

꾸Jun·2024년 10월 9일
0

🍎 iOS

목록 보기
10/12

토큰, 세션, 쿠키가 필요한 이유

클라이언트와 서버는 데이터를 주고 받는 과정을 HTTP Protocol 방식을 사용한다.

HTTP Protocol의 특징

  • connectionless(비연결성지향)

    • HTTP는 비연결성 지향 프로토콜이다. 이는 클라이언트가 서버에 요청을 보내고 서버가 응답을 반환한 후, 그 연결이 즉시 종료된다는 것을 의미한다. 각 요청/응답 쌍은 독립적으로 처리되며, 지속적인 연결을 유지하지 않는다.
    • 클라이언트와 서버 간의 지속적인 연결이 필요하지 않은 경우에는 효율적이지만, 사용자의 상태를 유지해야 하는 경우에는 문제가 될 수 있다.
  • stateless(무상태)

    • HTTP는 무상태 프로토콜이다. 이는 서버가 각 요청을 독립적으로 처리하며, 이전 요청의 상태를 저장하지 않는다는 것을 의미한다. 클라이언트의 각 요청은 완전한 정보를 포함해야 하며, 서버는 요청 간의 상태를 유지하지 않는다.
    • 서버가 클라이언트의 상태를 기억하지 않기 때문에, 사용자가 로그인했는지, 어떤 페이지를 보고 있는지 등의 정보를 유지할 수 없다.

--> 즉, 사용자의 정보를 유지할 때 문제가 발생한다. 이 문제를 해결하기 위해 쿠키, 세션은 주로 사용자의 상태 정보를 저장하고 전달하는 데 사용되며, 토큰은 인증과 권한 부여를 위한 해결책을 제시해준다.


iOS 앱 기준

iOS 앱에서도 HTTP Protocol을 사용하여 서버와 데이터를 주고받는다. iOS 앱은 자체적인 저장소와 메모리 관리 방식을 통해 HTTP의 비연결성과 무상태성 문제를 해결한다.

iOS 앱에서의 상태 관리

  • 비연결성(Connectionless)

    • iOS 앱은 URLSession을 사용하여 서버와의 네트워크 요청을 관리한다. 각 요청은 독립적으로 처리되며, 요청이 완료되면 연결이 종료된다.
    • 앱이 실행 중일 때만 유효한 임시 데이터를 메모리에 저장하여 관리한다. 앱이 종료되면 메모리에 저장된 데이터는 사라진다.
  • 무상태(Stateless)

    • iOS 앱은 서버가 각 요청을 독립적으로 처리하며, 이전 요청의 상태를 저장하지 않는 HTTP의 무상태성을 보완하기 위해 다양한 방법을 사용한다.
    • 사용자의 상태를 유지하기 위해 쿠키와 토큰을 사용하여 서버와의 인증 및 상태를 관리한다.

--> 즉, 사용자의 정보를 유지할 때 문제가 발생할 수 있다. 이 문제를 해결하기 위해 iOS 앱에서는 다음과 같은 방법을 사용한다.


쿠키, 세션, 토큰

iOS 앱에서 쿠키, 세션, 토큰은 각각 다른 방식으로 사용자 상태와 인증 정보를 관리한다.

쿠키(Cookie)

  • 저장 위치: 쿠키는 HTTPCookieStorage를 통해 앱의 저장소에 파일 형태로 저장된다. 앱이 종료되더라도 쿠키는 유지된다.
  • 사용 목적: 주로 서버와의 지속적인 상태를 유지하기 위해 사용된다. 예를 들어, 사용자가 로그인한 상태를 유지하거나, 사용자 설정을 저장하는 데 사용될 수 있다.
  • 특징: 쿠키는 서버가 클라이언트에 설정하여 클라이언트가 이후 요청 시 서버에 자동으로 전송된다. 이는 서버가 클라이언트를 식별하고 상태를 유지하는 데 도움을 준다.
// 쿠키는 HTTPCookieStorage를 통해 관리됨
func setCookie() {
        let cookieProperties: [HTTPCookiePropertyKey: Any] = [
            .domain: "example.com",
            .path: "/",
            .name: "userSession",
            .value: "123456",
            .secure: "TRUE",
            .expires: Date().addingTimeInterval(3600)
        ]
        
        if let cookie = HTTPCookie(properties: cookieProperties) {
            HTTPCookieStorage.shared.setCookie(cookie)
            print("쿠키 설정 완료: \(cookie)")
        }
    }

세션(Session)

  • 저장 위치: 세션 정보는 URLSession을 통해 앱의 메모리에 저장된다. 앱이 종료되면 세션 정보는 사라진다.
  • 사용 목적: 앱이 실행 중일 때만 유효한 임시 데이터를 저장하는 데 사용된다. 예를 들어, 사용자가 앱을 사용하는 동안의 임시 데이터를 관리하는 데 유용하다.
  • 특징: 세션은 주로 클라이언트와 서버 간의 일시적인 연결을 관리하는 데 사용된다. 세션 ID는 서버가 클라이언트를 식별하는 데 사용되며, 이는 주로 쿠키에 저장된다.
// 세션은 URLSession을 통해 관리됨
func fetchData() {
        guard let url = URL(string: "https://example.com/api/data") else { return }
        
        let session = URLSession.shared
        let task = session.dataTask(with: url) { data, response, error in
            if let error = error {
                print("Error: \(error.localizedDescription)")
                return
            }
            
            if let data = data {
                print("Response data: \(String(data: data, encoding: .utf8) ?? "")")
            }
        }
        
        task.resume()
    }

토큰(Token)

  • 저장 위치: 토큰은 보안상의 이유로 Keychain에 저장된다. 앱이 종료되더라도 토큰은 안전하게 유지된다.
  • 사용 목적: 사용자 인증과 권한 부여를 위해 사용된다. 사용자가 로그인한 후, 서버와의 모든 요청에 대해 인증을 수행하는 데 사용된다. JWT(JSON Web Token)와 같은 형식이 일반적이다.
  • 특징: 토큰은 클라이언트가 서버에 요청을 보낼 때 HTTP 헤더에 포함되어 전송된다. 서버는 토큰을 검증하여 사용자의 신원을 확인하고 요청을 처리한다. 토큰은 상태를 유지하지 않으며, 클라이언트가 상태 정보를 직접 관리해야 한다.
// 토큰은 주로 Keychain에 저장하여 사용
// 예시 코드는 토큰을 사용하여 요청을 보내는 과정
func authenticateWithToken(token: String) {
        guard let url = URL(string: "https://example.com/api/authenticate") else { return }
        
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        
        let session = URLSession.shared
        let task = session.dataTask(with: request) { data, response, error in
            if let error = error {
                print("Error: \(error.localizedDescription)")
                return
            }
            
            if let httpResponse = response as? HTTPURLResponse {
                print("Status code: \(httpResponse.statusCode)")
            }
            
            if let data = data {
                print("Response data: \(String(data: data, encoding: .utf8) ?? "")")
            }
        }
        
        task.resume()
    }

이러한 차이점들을 통해 iOS 앱은 사용자 상태와 인증 정보를 효과적으로 관리하며, 각 기술은 특정한 목적과 상황에 맞게 사용된다.



iOS에서는 쿠키보다 토큰을 주로 사용❗️

iOS에서는 쿠키보다 토큰 기반 인증이 주로 사용된다. 이는 특히 보안과 확장성을 고려한 선택이다. 쿠키는 웹 브라우저 환경에서 자주 사용되지만, 모바일 애플리케이션에서는 주로 JWT 같은 토큰 기반 인증이 사용된다. 이를 이해하기 위해, 쿠키와 토큰 기반 인증 방식의 차이점과 iOS에서 토큰이 더 선호되는 이유를 코드 예시와 함께 아래에서 설명하겠다.


1. 쿠키 기반 인증과 iOS에서의 한계

쿠키는 HTTP 응답 시 서버가 클라이언트에게 쿠키를 설정하고, 이후 요청마다 자동으로 쿠키를 전송하여 세션 상태를 유지하는 방식이다. 그러나 쿠키는 웹 환경에 최적화되어 있어, 브라우저 기반 애플리케이션에서는 편리하게 동작하지만 모바일 앱에서는 몇 가지 한계가 있다.

iOS에서 쿠키 사용의 한계

  • 저장소 제한: iOS에서 쿠키는 HTTPCookieStorage를 통해 저장된다. 하지만 이는 앱 외부 저장소와 관련된 보안 이슈와 제약이 있을 수 있다.
  • CSRF 공격 취약성: 쿠키는 자동으로 서버로 전송되기 때문에 CSRF(Cross-Site Request Forgery)와 같은 보안 이슈에 취약할 수 있다.
  • 앱 환경의 차이: 웹 브라우저는 쿠키를 자동으로 처리하지만, iOS 앱에서는 이 과정을 개발자가 직접 처리해야 하므로 복잡도가 증가한다.

따라서 iOS에서는 쿠키 대신 토큰 기반 인증을 선호한다.


2. 토큰 기반 인증의 장점

토큰 기반 인증, 특히 JWT를 사용한 인증 방식은 iOS에서 주로 사용되며, 이는 앱의 보안성과 확장성을 높여준다.

iOS에서 토큰 기반 인증의 장점

  • 보안성: JWT와 같은 토큰은 앱이 종료되더라도 안전한 저장소인 Keychain에 저장되어, 앱이 다시 시작되어도 안전하게 인증할 수 있다.
  • 확장성: 쿠키는 상태를 유지하는 데 사용되지만, 토큰은 무상태(Stateless) 방식을 유지할 수 있어, 서버는 각 요청에 대한 상태를 저장할 필요가 없다.
  • CSRF 방지: 토큰은 클라이언트가 명시적으로 HTTP 헤더에 포함하여 전송하므로, CSRF 공격에 덜 취약하다.

3. JWT 기반 인증 흐름

JWT는 JSON 형식으로 인코딩된 토큰으로, 클라이언트가 로그인한 후 서버에서 발급받아 이후 모든 요청에 포함시켜 서버에 인증을 요청하는 방식이다.

흐름

  1. 사용자가 로그인 정보를 서버에 보낸다.
  2. 서버는 로그인 정보가 유효한지 확인하고, 유효하다면 JWT를 발급한다.
  3. 클라이언트는 이 JWT를 Keychain에 저장한다.
  4. 이후 모든 요청에서 JWT를 HTTP 헤더에 포함하여 서버에 전송한다.
  5. 서버는 JWT를 확인하고 유효하다면 요청을 처리한다.

4. JWT를 이용한 iOS 코드 예시

1) 토큰 저장 및 관리 (Keychain 사용)

import Security

class KeychainService {
    static func saveToken(token: String, for account: String) {
        let tokenData = token.data(using: .utf8)!
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: account,
            kSecValueData as String: tokenData
        ]
        
        SecItemAdd(query as CFDictionary, nil)
    }
    
    static func getToken(for account: String) -> String? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: account,
            kSecReturnData as String: kCFBooleanTrue!,
            kSecMatchLimit as String: kSecMatchLimitOne
        ]
        
        var dataTypeRef: AnyObject? = nil
        let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
        
        if status == errSecSuccess {
            if let data = dataTypeRef as? Data {
                return String(data: data, encoding: .utf8)
            }
        }
        return nil
    }
    
    static func deleteToken(for account: String) {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: account
        ]
        
        SecItemDelete(query as CFDictionary)
    }
}

위 코드는 Keychain에 JWT를 저장하고 불러오며 삭제하는 과정을 나타낸다. Keychain은 앱이 재시작되어도 안전하게 사용자 토큰을 저장할 수 있는 저장소이다.

2) 토큰을 이용한 인증 요청

import Foundation

func fetchAuthenticatedData() {
    // Keychain에서 저장된 토큰을 가져온다.
    guard let token = KeychainService.getToken(for: "authToken") else {
        print("토큰을 찾을 수 없다.")
        return
    }
    
    guard let url = URL(string: "https://example.com/api/protected") else { return }
    
    var request = URLRequest(url: url)
    request.httpMethod = "GET"
    request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
    
    let session = URLSession.shared
    let task = session.dataTask(with: request) { data, response, error in
        if let error = error {
            print("에러: \(error.localizedDescription)")
            return
        }
        
        if let httpResponse = response as? HTTPURLResponse {
            print("상태 코드: \(httpResponse.statusCode)")
        }
        
        if let data = data {
            print("응답 데이터: \(String(data: data, encoding: .utf8) ?? "")")
        }
    }
    
    task.resume()
}

이 코드는 토큰을 이용한 서버 인증 요청을 나타낸다. Keychain에서 저장된 JWT를 불러와, 요청의 Authorization 헤더에 Bearer 토큰으로 포함시켜 서버에 인증된 요청을 보낸다.


5. 결론

iOS에서는 쿠키 대신 토큰 기반 인증(JWT)이 더 자주 사용된다. 이는 보안성, 확장성, 그리고 모바일 애플리케이션에 더 적합한 무상태 인증 방식 때문이다. Keychain과 같은 안전한 저장소를 이용해 토큰을 관리하며, HTTP 헤더를 통해 인증된 요청을 서버에 보냄으로써, 안전하고 효율적인 인증 흐름을 구현할 수 있다.



사진 및 참고 자료

profile
꾸준🐢

0개의 댓글