클라이언트와 서버는 데이터를 주고 받는 과정을 HTTP Protocol 방식을 사용한다.
HTTP Protocol의 특징
connectionless(비연결성지향)
stateless(무상태)
--> 즉, 사용자의 정보를 유지할 때 문제가 발생한다. 이 문제를 해결하기 위해 쿠키, 세션은 주로 사용자의 상태 정보를 저장하고 전달하는 데 사용되며, 토큰은 인증과 권한 부여를 위한 해결책을 제시해준다.
iOS 앱에서도 HTTP Protocol을 사용하여 서버와 데이터를 주고받는다. iOS 앱은 자체적인 저장소와 메모리 관리 방식을 통해 HTTP의 비연결성과 무상태성 문제를 해결한다.
비연결성(Connectionless)
URLSession
을 사용하여 서버와의 네트워크 요청을 관리한다. 각 요청은 독립적으로 처리되며, 요청이 완료되면 연결이 종료된다.무상태(Stateless)
--> 즉, 사용자의 정보를 유지할 때 문제가 발생할 수 있다. 이 문제를 해결하기 위해 iOS 앱에서는 다음과 같은 방법을 사용한다.
iOS 앱에서 쿠키, 세션, 토큰은 각각 다른 방식으로 사용자 상태와 인증 정보를 관리한다.
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)")
}
}
URLSession
을 통해 앱의 메모리에 저장된다. 앱이 종료되면 세션 정보는 사라진다.// 세션은 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()
}
Keychain
에 저장된다. 앱이 종료되더라도 토큰은 안전하게 유지된다.// 토큰은 주로 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에서는 쿠키보다 토큰 기반 인증이 주로 사용된다. 이는 특히 보안과 확장성을 고려한 선택이다. 쿠키는 웹 브라우저 환경에서 자주 사용되지만, 모바일 애플리케이션에서는 주로 JWT 같은 토큰 기반 인증이 사용된다. 이를 이해하기 위해, 쿠키와 토큰 기반 인증 방식의 차이점과 iOS에서 토큰이 더 선호되는 이유를 코드 예시와 함께 아래에서 설명하겠다.
쿠키는 HTTP 응답 시 서버가 클라이언트에게 쿠키를 설정하고, 이후 요청마다 자동으로 쿠키를 전송하여 세션 상태를 유지하는 방식이다. 그러나 쿠키는 웹 환경에 최적화되어 있어, 브라우저 기반 애플리케이션에서는 편리하게 동작하지만 모바일 앱에서는 몇 가지 한계가 있다.
HTTPCookieStorage
를 통해 저장된다. 하지만 이는 앱 외부 저장소와 관련된 보안 이슈와 제약이 있을 수 있다.따라서 iOS에서는 쿠키 대신 토큰 기반 인증을 선호한다.
토큰 기반 인증, 특히 JWT를 사용한 인증 방식은 iOS에서 주로 사용되며, 이는 앱의 보안성과 확장성을 높여준다.
JWT는 JSON 형식으로 인코딩된 토큰으로, 클라이언트가 로그인한 후 서버에서 발급받아 이후 모든 요청에 포함시켜 서버에 인증을 요청하는 방식이다.
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은 앱이 재시작되어도 안전하게 사용자 토큰을 저장할 수 있는 저장소이다.
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 토큰으로 포함시켜 서버에 인증된 요청을 보낸다.
iOS에서는 쿠키 대신 토큰 기반 인증(JWT)이 더 자주 사용된다. 이는 보안성, 확장성, 그리고 모바일 애플리케이션에 더 적합한 무상태 인증 방식 때문이다. Keychain과 같은 안전한 저장소를 이용해 토큰을 관리하며, HTTP 헤더를 통해 인증된 요청을 서버에 보냄으로써, 안전하고 효율적인 인증 흐름을 구현할 수 있다.