JWT, OAuth 2.0, HTTP ์ฟ ํค๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ ์น ์ค์ฌ ์ธ์ ๊ด๋ฆฌ๋ฅผ iOS ๋ค์ดํฐ๋ธ ์ฝ๋์ WKWebView ๊ฐ์ ๋๊ธฐํํ๋ ๋ฐฉ๋ฒ๊ณผ ๋๊ด์ ์ ๋ฆฌํฉ๋๋ค. ์๋/์๋ ์ฟ ํค ์ฒ๋ฆฌ, ๋จ์ผ ์ง์ค ๊ณต๊ธ์, ๋ ์ด์ค ์ปจ๋์ , WKWebView ์ฟ ํค ์ ์ค ์ด์์ ๋ค์ดํฐ๋ธ ์์ ๊ณ์ธต ์ฐํ์ฑ , ํฅํ ๋์๊น์ง ๋ค๋ฃน๋๋ค.
์ฝ๋ ๋ฐ 15๋ถ
2024๋ 2์ 16์ผ
์ฐ์ ์์ 1: (๊ฑฐ์) ๋ชจ๋ ๋์งํธ ๋น์ฆ๋์ค ์์ด๋์ด์ ๊ธฐ๋ฐ์ ์ฌ์ฉ์ ์๋ณ๊ณผ ๊ตฌ๋งค/๊ตฌ๋ ์ ํตํ ์๋น์ค ์ ๊ณต์ ๋๋ค. ์ด ๋์ ๋๋ถ๋ถ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ธฐ๋ณธ ๋ก์ง์ด์ ๋น์ฆ๋์ค ์ฑ๊ณต์ ํต์ฌ์ ๋๋ค.
National Media & Tech๋ JSON Web Token, OAuth 2.0, HTTP ์ฟ ํค๋ฅผ ๋ฐํ์ผ๋ก ํ ์น ์ค์ฌ์ ์ธ์ ๊ด๋ฆฌ ๋ฐฉ์์ ์ฌ์ฉํฉ๋๋ค.
์ด ๊ธ์ ์ฃผ๋ก ์น ๊ธฐ๋ฐ์ธ ์ธ์ ๊ด๋ฆฌ๋ฅผ iOS์ ์ ์ฉํ ๋์ ๋์ ๊ณผ์ ๋ฅผ ๋ค๋ฃน๋๋ค.
๊ธ ๊ตฌ์ฑ์ ํท๊ฐ๋ฆฌ์ง ๋ง์ธ์: ๋จผ์ ๊ธฐ๋ณธ์ ์ค๋ช ํ๊ณ , ์ฐ๋ฆฌ์ ์ค๋์ธ์ด๋ฅผ ๋ค๋ ค์ค ๋ค, ์ต์ข ์ ์ผ๋ก ์๋ํ๋ ํด๋ฒ์ผ๋ก ์ด๋๋๋ค. ๊ทธ๋ฌ๋ ๋ถ๋ ๊ณ์ ์ฝ์ผ๋ฉฐ ์ฐ๋ฆฌ์ ์ํ์ฐฉ์ค๋ฅผ ํผํ์ธ์โฆ
JSON Web Token(JWT)์ ์ ๋ณด๋ฅผ JSON ๊ฐ์ฒด๋ก ์์ ํ๊ฒ ์ ์กํ๊ธฐ ์ํ ํ์ค(RFC 7519)์ ๋๋ค. ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ๊ณผ ์ ๋ขฐ๋ฅผ ์ํด ๋์งํธ ์๋ช ์ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์๋ช ์ HMAC์ ํตํ ์ํฌ๋ฆฟ ๋๋ RSA/ECDSA ๊ณต๊ฐ/๊ฐ์ธํค ์์ผ๋ก ์ํํฉ๋๋ค. JWT๋ ๊ธฐ๋ฐ์ฑ์ ์ํด ์ํธํํ ์๋ ์์ง๋ง, ์ฌ๊ธฐ์๋ ํค๋์ ํ์ด๋ก๋ ์ ์ฒด๋ฅผ ์๋ช ํ์ฌ ํด๋ ์์ ๋ฌด๊ฒฐ์ฑ์ ๋ณด์ฅํ๋ โ์๋ช ๋ ํ ํฐโ์ ์ด์ ์ ๋ก๋๋ค.
JWT๋ ํนํ ๋ ๊ฐ์ง ์๋๋ฆฌ์ค์์ ์ ์ฉํฉ๋๋ค:
์์ฝํ๋ฉด, JWT๋ ๋์งํธ ์๋ช ์ ํ์ฉํด ๋ฐ์ดํฐ์ ๋ฌด๊ฒฐ์ฑ๊ณผ ์ง์์ฑ์ ๋ณด์ฅํ๋ฉด์ ์ธ์ฆ๊ณผ ์์ ํ ์ ๋ณด ๊ตํ์ ์ํ ๊ฐ๊ฒฐํ๊ณ ์ ์ฐํ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค.
์ฐธ๊ณ ๋ก Google FirebaseAuthentication๋ ๊ฐ์ ์ ๊ทผ์ ์ฌ์ฉํฉ๋๋ค.
OAuth 2.0์ ์ ํ๋ฆฌ์ผ์ด์ ์ด Facebook, GitHub, Google ๊ฐ์ HTTP ์๋น์ค์ ์ฌ์ฉ์ ๊ณ์ ์ ์ ํ๋ ์ ๊ทผ ๊ถํ์ ์ป๋๋ก ํ๋ ์ธ๊ฐ ํ๋ ์์ํฌ์ ๋๋ค. ์ฌ์ฉ์ ์ธ์ฆ์ ํด๋น ์๋น์ค์ ์์ํ๊ณ , ์๋ํํฐ ์ฑ์ด ์ฌ์ฉ์ ๊ณ์ ์ ์ ๊ทผํ๋๋ก ํ์ฉํฉ๋๋ค. โ์ก์ธ์ค ํ ํฐโ์ ์๋ช ์ด ์งง์ ์ฌ์ฉ์ ๋์ API ์์ฒญ์ ์ธ๊ฐํ๋ ๋ฐ ์ฐ์ด๊ณ , โ๋ฆฌํ๋ ์ ํ ํฐโ์ ์๋ช ์ด ๊ธธ์ด ์ฌ์ฉ์๊ฐ ๋ค์ ๋ก๊ทธ์ธํ ํ์ ์์ด ์๋ก์ด ์ก์ธ์ค ํ ํฐ์ ๋ฐ๊ธ๋ฐ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
์ฟ ํค๋ ๋ชฉ์ ์ ๋ฐ๋ผ ์ฌ๋ฌ ์ข ๋ฅ๊ฐ ์์ต๋๋ค.
์์ ์ฟ ํค(persistent cookie)๋ ์ง์ ๋ ๋ง๋ฃ์ผ/๊ธฐ๊ฐ๊น์ง ๋ธ๋ผ์ฐ์ ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํฉ๋๋ค. ์ธ์ ์ข ๋ฃ ์ ์ญ์ ๋๋ ์ธ์ ์ฟ ํค์ ๋ฌ๋ฆฌ ์ฌ๋ฌ ์ธ์ ์ ๊ฑธ์ณ ์ ์ง๋ฉ๋๋ค. ์ฌ์ฉ์๊ฐ ๊ด๋ จ ์น์ฌ์ดํธ๋ฅผ ๋ค์ ๋ฐฉ๋ฌธํ๊ฑฐ๋ ๊ด๊ณ ๋ฑ์ผ๋ก ๋ค๋ฅธ ์ฌ์ดํธ์์ ํด๋น ๋ฆฌ์์ค๋ฅผ ๋ณผ ๋๋ง๋ค ์๋ฒ๋ก ์ ๋ณด๋ฅผ ์ ์กํฉ๋๋ค. ๋๋ถ์ ์ ์ ๋ฅผ ์ฅ๊ธฐ๊ฐ ์ถ์ ํ๊ฑฐ๋, ๋ฐฉ๋ฌธ ๊ฐ ๋ก๊ทธ์ธ ์ํ๋ฅผ ์ ์งํด ๋ฐ๋ณต ๋ก๊ทธ์ธ ์ ๋ ฅ์ ์ค์ด๋ ๋ฐ ์ ์ฉํฉ๋๋ค.
HTTP ์ฟ ํค๋ฅผ HTTP ํค๋๋ก ์ฃผ๊ณ ๋ฐ๋ ๋ฐฉ์์ ๋ธ๋ผ์ฐ์ ๊ธฐ๋ฐ ์น ์ธ์ ๊ด๋ฆฌ์ ์ฌ์ค์ ํ์ค์ด๋ฉฐ, ๋ธ๋ผ์ฐ์ ๊ฐ ์ฟ ํค ํ์ฐ์คํคํ์ ๋งก์ต๋๋ค.
์น์์๋ ๋ชจ๋ ๊ฒ์ด ๋ธ๋ผ์ฐ์ ๊ธฐ๋ฐ์ด์ง๋ง, ์ฑ์ ๋ค์ดํฐ๋ธ๋ก ๊ตฌํํ๋ฉด ํฌ๊ฒ ์ธ ๊ฐ์ง ์ด์ ์ด ์์ต๋๋ค:
PWA๋ Flutter ๊ฐ์ ๋ฉํฐํ๋ซํผ ๋๊ตฌ๋ก ์ ์ฌ ๊ธฐ๋ฅ์ ๊ตฌํํ ์ ์์ด๋, iPhone์์ ์ต์ ์ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ตฌํํ๋ ค๋ฉด ๋ค์ดํฐ๋ธ๊ฐ ์ฌ์ ํ ์ฐ์ํฉ๋๋ค.
์ธ์ ์ ๋ค์ดํฐ๋ธ ํธ์ถ๋ก ์์ํ๋, ์ธ์ ์ ๋ณด๋ฅผ ๋ด์ ๋ชจ๋ ์ฟ ํค๋ฅผ ์์ง ๋ง๊ณ ์ฃผ๊ณ ๋ฐ์์ผ ํฉ๋๋ค.
swiftlet session = URLSession.shared if let url = URL(string: "https://example.com") { var request = URLRequest(url: url) request.httpMethod = "GET" let task = session.dataTask(with: request) { data, response, error in if let error = error { print("Error: \(error.localizedDescription)") return } } task.resume() }
ํต์ฌ ํฌ์ธํธ
URLSession ๊ตฌ์ฑ์์๋ iOS๊ฐ ์ฟ ํค๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค. ์๋ต์์ ๋ฐ์ ์ฟ ํค ์ ์ฅ, ๋์ผ ์๋ฒ ์์ฒญ ์ ์ฟ ํค ์๋ ์ ์ก ๋ฑ์ด ํฌํจ๋ฉ๋๋ค.URLSession.shared๋ URLSessionConfiguration.default๋ก ๋ง๋ ์ธ์
์ ์๋ ์ฟ ํค ์ฒ๋ฆฌ๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ปค์คํ
๊ตฌ์ฑ์ ๋ง๋ค ๊ฒฝ์ฐ, ์ง์ ์ฟ ํค๋ฅผ ๊ด๋ฆฌํ๋ ค๋ ์๋๊ฐ ์๋๋ผ๋ฉด ์๋ ์ฟ ํค ์ฒ๋ฆฌ๋ฅผ ๋นํ์ฑํํ์ง ๋ง์ธ์.์ด์ HTTPCookieStorage์ ์ ์ฅ๋ ์ธ์
์ ๋ณด๋ฅผ WKWebView์ WKCookieStore๋ก ๋๊ฒจ์ผ ํฉ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก HTTPCookieStorage์ ์ ์ฅ๋ ์ฟ ํค๋ WKWebView์ WKCookieStore์ ์๋ ๊ณต์ ๋์ง ์์ผ๋ฉฐ, ๊ทธ ๋ฐ๋๋ ๋ง์ฐฌ๊ฐ์ง์
๋๋ค. ์ด๊ฒ์ WebKit์ ํ๋ก์ธ์ค ๋ถ๋ฆฌ ์ํคํ
์ฒ๋ก WebView๊ฐ ์ฑ๊ณผ ๋ค๋ฅธ ํ๋ก์ธ์ค์์ ๋์ํ๊ธฐ ๋๋ฌธ์
๋๋ค. ์ฑ๋ฅ/๋ณด์์ ๋์ฌ์ฃผ๋ ์ค๊ณ์ง๋ง, ๊ทธ๋งํผ WebView์ ์ฑ์ HTTP ์์ฒญ ๊ฐ ์ฟ ํค ๊ณต์ ๊ฐ ๊ฐ๋จํ์ง ์์ต๋๋ค.
WKWebView์ ์ฟ ํค ์ ์ฅ์์ ์ฟ ํค๋ฅผ ์ฝ์
ํ๋ ค๋ฉด ์ฟ ํค๋ฅผ ์ํํ๋ฉฐ setCookie๋ก ์ถ๊ฐํ์ธ์. setCookie๋ ๋น๋๊ธฐ์ด๋ฏ๋ก, ๋ชจ๋ ์ฟ ํค ์ค์ ํ ์์
(์: ์น ์์ฒญ ๋ก๋)์ด ํ์ํ๋ฉด await๋ก ์๋ฃ๋ฅผ ๊ธฐ๋ค๋ฆฝ๋๋ค.
swiftfor cookie in cookies { await webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie) } // ๋ชจ๋ ์ฟ ํค ์ค์ ํ ๋ก๋ if let url = URL(string: "https://example.com") { let request = URLRequest(url: url) webView.load(request) }
์ข์์ โ ๊ทธ๋ฐ๋ฐ ๋ฐฑ๊ทธ๋ผ์ด๋์ ๋ค์ดํฐ๋ธ ํธ์ถ๋ก ์ฟ ํค๊ฐ ์ ๋ฐ์ดํธ๋๋ฉด ์ด๋ป๊ฒ ๋์ฃ ?
์ด ๊ฒฝ์ฐ ์ฟ ํค๋ฅผ ๋ค์ ์ ๋ฌํ๊ณ , ์คํ ์ค์ธ ์น ์ปจํ ์คํธ๊ฐ ์ฟ ํค๋ฅผ ์ฌ์ฝ๋๋ก WKWebView๋ฅผ ๋ฆฌ๋ก๋ํด์ผ ํฉ๋๋ค.
๊ฐ๋ฅ์ ํด ๋ณด์ด๋๋ฐ, ๋ง์ฝ WebView ์ค์ค๋ก ์ฟ ํค๋ฅผ ์ ๋ฐ์ดํธํ๋ฉด์(์: ์๋ฐ์คํฌ๋ฆฝํธ๋ ๋ค๋น๊ฒ์ด์ ์ค ์๋ฒ์ set-cookie)?
์โฆ ์ข์ ์ง์ ์ ๋๋ค! ๊ทธ๋ฌ๋ฉด WKHTTPCookieStoreObserver๋ก WKHTTPCookieStore ๋ณ๊ฒฝ์ ๊ตฌ๋ ํ์ฌ, ๋ณ๊ฒฝ๋ถ์ ๋ค์ดํฐ๋ธ ํธ์ถ์ด ์ฌ์ฉํ๋ HTTPCookieStorage์ ๋ค์ ๋๊ธฐํํด์ผ ํฉ๋๋ค.
swiftfunc copyCookiesToSharedStorage(from cookieStore: WKHTTPCookieStore) async { let cookies = await cookieStore.getAllCookiesAsync() for cookie in cookies { HTTPCookieStorage.shared.setCookie(cookie) } }
์ํฐ๋ฅผ ๋๋ฅด๊ฑฐ๋ ํด๋ฆญํ์ฌ ์ด๋ฏธ์ง๋ฅผ ์ ์ฒด ํฌ๊ธฐ๋ก ๋ณด๊ธฐ

HTTPCookieStorage์ WKHTTPCookieStore์ ์์ ๋๊ธฐํ(Mermaid)
์ ๊น๋ง์. ๋ญ๊ฐ ์๋ชป๋ ๋๋์ด ๋๋ค์.
์์๊ฐ ๊ฑฐ๊พธ๋ก์๋ ๊ฑด ์๋๊น์!
๋จ์ผ ์ง์ค ๊ณต๊ธ์์ด ์์ด์ผ ํ๊ณ , ์ปค์คํ ๋๊ธฐํ๋ฅผ ํ๋ฉด ๋์์ ํธ์ถ๋ ๋ ๊ฒฝ์ ์ํ๊ฐ ์๊ธธ ์๋ ์์ต๋๋คโฆ
WKHTTPCookieStore๋ฅผ ๋จ์ผ ์ง์ค ๊ณต๊ธ์์ผ๋ก ์ฐ๋ฉด, WebView ๋ด์์ ์ผ์ด๋๋ ๋ชจ๋ ๋ณ๊ฒฝ์ ๋ค์ดํฐ๋ธ ํธ์ถ์์ ์ ๊ทผํ ์ ์๊ณ , ๋ฐ๋๋ก ๋ค์ดํฐ๋ธ ํธ์ถ๋ WKHTTPCookieStore์ ์ง์ ์ฐ๋ฉด ๊ทธ ๋ณ๊ฒฝ์ด ๊ณง๋ฐ๋ก WebView์ ๋ฐ์๋ฉ๋๋ค.
์ํฐ๋ฅผ ๋๋ฅด๊ฑฐ๋ ํด๋ฆญํ์ฌ ์ด๋ฏธ์ง๋ฅผ ์ ์ฒด ํฌ๊ธฐ๋ก ๋ณด๊ธฐ

WKHTTPCookieStore๋ฅผ ๋จ์ผ ์ง์ค ๊ณต๊ธ์์ผ๋ก(Mermaid)
๋ค์ดํฐ๋ธ ์์ฒญ์ ํญ์ WKHTTPCookieStore๋ฅผ ์ฝ๊ณ ์ด๋ค ์ด์ WKHTTPCookieStore๋ฅผ ๋จ์ผ ์ง์ค ๊ณต๊ธ์์ผ๋ก ๋ง๋ค๋ฉด ๋ชจ๋ WKWebView์ ์ฟ ํค๊ฐ ์๋ ์ ๊ณต๋ฉ๋๋ค. ๋ค๋ง ๋ค์ดํฐ๋ธ ์์ฒญ ์์๋ ์ฟ ํค๋ฅผ ๋ช ์์ ์ผ๋ก ์ค์ ํ๊ณ , ๊ฒฐ๊ณผ ์ฟ ํค๋ฅผ ๋ค์ WKWebsiteDataStore์ ์จ์ค ๋ค ๋ฆฌ๋ก๋ํ์ฌ ๋ชจ๋ WKWebView์์ ์ด์ฉ ๊ฐ๋ฅํ๊ฒ ํด์ผ ํฉ๋๋ค.
swiftWKWebsiteDataStore.default().httpCookieStore.getAllCookies { cookies in for cookie in cookies { HTTPCookieStorage.shared.setCookie(cookie) } if let url = URL(string: "https://example.com") { var request = URLRequest(url: url) let task = session.dataTask(with: request) { data, response, error in if let error = error { print("Request error: \(error)") return } guard let httpResponse = response as? HTTPURLResponse else { print("Invalid response") return } if let headerFields = httpResponse.allHeaderFields as? [String: String], let url = response?.url { let cookies = HTTPCookie.cookies(withResponseHeaderFields: headerFields, for: url) for cookie in cookies { DispatchQueue.main.async { WKWebsiteDataStore.default().httpCookieStore.setCookie(cookie) { // ์๋ฃ ํธ๋ค๋ฌ } } } } } task.resume() } }
์ค๋ช ํ ์ ๊ทผ์ ๋ค์ดํฐ๋ธ ์์ฒญ๊ณผ WKWebView์๋ ์ ์๋ํ์ง๋ง, SFSafariController๋ Safari์๋ ์ ํฉํ์ง ์์ต๋๋ค. Safari ๋ทฐ์ ์ปจํ ์คํธ๋ ์ฑ๊ณผ ์์ ํ ๊ฒฉ๋ฆฌ๋์ด ์์ด ์ฟ ํค๋ฅผ ์ ๋ฌํ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
์ํฐ๋ฅผ ๋๋ฅด๊ฑฐ๋ ํด๋ฆญํ์ฌ ์ด๋ฏธ์ง๋ฅผ ์ ์ฒด ํฌ๊ธฐ๋ก ๋ณด๊ธฐ

๋น๋๊ธฐ ๋ก๋
WKWebsiteDataStore๋ WebKit์ ์ผ๋ถ๋ก, ์ฟ ํค/์บ์/๋ก์ปฌ ์คํ ๋ฆฌ์ง ๋ฑ ์น์ฌ์ดํธ ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค. ์ฑ ์๋ช
์ฃผ๊ธฐ ์ด๋ฐ, ์๋ฅผ ๋ค์ด applicationDidLoad ์์ ์ WKWebsiteDataStore๋ฅผ ์กฐํํ๋ฉด ๋น์ด ๋ณด์ผ ์ ์์ต๋๋ค. ์ด์ ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
WKWebsiteDataStore๋ก ๋ถ๋ฌ์ค๋ ๊ณผ์ ์ โ๋น๋๊ธฐโ์
๋๋ค. ์ฑ ๋ก๋์ ์ฆ์ ๋ด์ฉ์ ํ์ธํ๋ฉด ๋ก๋ฉ์ด ์์ง ๋๋์ง ์์์ ์ ์์ต๋๋ค.WKWebsiteDataStore๊ฐ applicationDidLoad ์์ ์ ์์ ํ ์ด๊ธฐํ๋์ง ์์์ ์ ์์ต๋๋ค. ์ฑ์ด ์น ์ปจํ
์ธ ๋ฅผ ์ด๋ป๊ฒ ์ด๊ธฐํ/์ ๊ทผํ๋์ง์ ๋ฐ๋ผ ๋ค๋ฆ
๋๋ค.WKWebView๊ฐ ์์ง ์๋ฌด ์ปจํ
์ธ ๋ ๋ก๋ํ์ง ์์๋ค๋ฉด, WKWebsiteDataStore๋ ๋น์ฐํ ๋น์ด ๋ณด์
๋๋ค.WKWebsiteDataStore๋ฅผ ์ ๊ทผํ๋ค๋ฉด ์ฑ ๋ด WKWebView์์ ๋ก๋ํ ์น ์ปจํ
์ธ ์ ๋ฐ์ดํฐ๊ฐ ๋ค์ด๊ฐ๋๋ค. ๋ค๋ง ํ์ด๋ฐ/์ด๊ธฐํ ์ํ์ ๋ฐ๋ผ ๊ฐ์ฉ ๋ฐ์ดํฐ๊ฐ ๋ฌ๋ผ ๋ณด์ผ ์ ์์ต๋๋ค.์ ๋น๋๊ธฐ ๋ฌธ์ ๋ฅผ ํผํ๊ธฐ ์ํด, ์ฟ ํค๋ฅผ ์ง์ ์ฝ๋ ๋์ fetchDataRecords(ofTypes:completionHandler:)๋ฅผ ์ฌ์ฉํ์ต๋๋ค. ์ง์ ์ ๊ทผํ๋ฉด ์ข
์ข
๋น ๊ฒ์ผ๋ก ๋ณด๊ณ ๋๊ณค ํ๊ธฐ ๋๋ฌธ์
๋๋ค.
swiftlet dataStore = WKWebsiteDataStore.default() let dataTypes = Set([WKWebsiteDataTypeCookies]) dataStore.fetchDataRecords(ofTypes: dataTypes) { records in for record in records { print("\(record.displayName) has the following cookies:") for cookie in record.cookies { print(cookie) } } }
๊ฒฉ๋ฆฌ๋ ์ฟ ํค & ์ธ์ ์ฟ ํค
WKWebView โ ๊ฐ WKWebView ์ธ์คํด์ค๋ ๊ธฐ๋ณธ ์์ ์คํ ์ด๋ฅผ ๊ณต์ ํ๊ฑฐ๋, ๋น์์ ์คํ ์ด(์ํฌ๋ฆฟ/์ฌํ๋ฆฌ์ ๊ฐ์ธ์ ๋ณด ๋ณดํธ ๋ชจ๋์ ์ ์ฌ)๋ฅผ ์ฌ์ฉํ๊ฑฐ๋, ํน์ ID๋ก ์์ ์คํ ์ด๋ฅผ ๊ตฌ์ฑํด ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ ์ ์์ต๋๋ค. ์์ธํ ๋ด์ฉ์ WKWebsiteDataStore ํด๋์ค๋ฅผ ์ฐธ๊ณ ํ์ธ์.
Medium์ ๋ฌด๋ฃ ๊ฐ์ ํ๊ณ ์ด ํ์์ ์ ๋ฐ์ดํธ๋ฅผ ๋ฐ์๋ณด์ธ์.
์ธ์
์ฟ ํค(์ฟ ํค ๊ฐ์ฒด์ isSessionOnly๊ฐ true)๋ ๋จ์ผ ํ๋ก์ธ์ค์๋ง ๊ตญํ๋๋ฉฐ ๊ณต์ ๋์ง ์์ต๋๋ค. ์ธ์
์ฟ ํค๋ ๋ง๋ฃ์ผ์ด ์๋ ๊ฒ์ผ๋ก ๋ธ๋ผ์ฐ์ ๊ฐ ์๋ณํฉ๋๋ค.
์ธ์ ์ ๋๊ธฐํํ๋ ค๊ณ ์ต์ ์ ๋คํ์ด๋, ์ฌ์ ํ ์ธ์ ์ด ์์๋๋ ๋ ์ด์ค ์ปจ๋์ ์ ๊ฒช์ ์ ์์ต๋๋ค.
๋ค์ดํฐ๋ธ ์ฑ์ด ์ธ์ ๊ฐฑ์ ์ ์์ํ๋ ๋์, WebView๋ ์์ฒด ๊ฐฑ์ ์ ์ํํด ๋ค์ดํฐ๋ธ ์ธ์ ์ ๋ฌดํจํํ๊ฑฐ๋ ๊ทธ ๋ฐ๋์ ์ํฉ์ด ์๊ธธ ์ ์์ต๋๋ค.
์ํฐ๋ฅผ ๋๋ฅด๊ฑฐ๋ ํด๋ฆญํ์ฌ ์ด๋ฏธ์ง๋ฅผ ์ ์ฒด ํฌ๊ธฐ๋ก ๋ณด๊ธฐ

๋์ผ ์ธ์ ์ ๋ํ WebView์ ๋ค์ดํฐ๋ธ์ ์ธ์ ์ฒ๋ฆฌ ๊ฐ ๋ ์ด์ค ์ปจ๋์ (Mermaid)
์ํฐ๋ฅผ ๋๋ฅด๊ฑฐ๋ ํด๋ฆญํ์ฌ ์ด๋ฏธ์ง๋ฅผ ์ ์ฒด ํฌ๊ธฐ๋ก ๋ณด๊ธฐ
์ดํต ํญ๋ฐ
๋ค์ดํฐ๋ธ ์์ฒญ์ ๊ฐ์ฅ ์ ํต์ ํ ์ ์๋ค๋ ์ ๊ณผ ๊ฒฐํฉํ์ฌ WKWebViewDataStorage๋ฅผ ๋จ์ผ ์ง์ค ๊ณต๊ธ์์ผ๋ก ์ผ๋ ๊ฒ์ ์์ฃผ ์ข์ ๊ฒฐ์ ์ฒ๋ผ ๋ณด์์ต๋๋ค๋งโฆ
๊ธฐ๋ณธ WKWebView ๊ตฌ์ฑ(๊ธฐ๋ณธ ์์์ฑ ์ฌ์ฉ)์์๋, WKWebViewDataStorage๊ฐ ๋ฌด์์๋ก ์ฟ ํค๋ฅผ ์์ด๋ฒ๋ ค ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์์๋๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค!!!
์ฐ๋ฆฌ๋ ์ฐํ์ฑ ์ผ๋ก ๋ ๋ฒ์งธ ๋ค์ดํฐ๋ธ ์์ ๋ง์ ๋์ ํ์ต๋๋ค. WKWebSiteDataStorage๊ฐ ์ฟ ํค๋ฅผ ์์ด๋ฒ๋ฆฐ ๊ฒฝ์ฐ์๋, ๋ค์ดํฐ๋ธ๋ก ์ธ์ ์ ๋ณต๊ตฌํด ๋ค์ WKWebSiteDataStorage์ ์ฟ ํค๋ฅผ ์ ๋ฌํ ์ ์๋๋ก ํญ์ ๋๋นํฉ๋๋ค.
์ํฐ๋ฅผ ๋๋ฅด๊ฑฐ๋ ํด๋ฆญํ์ฌ ์ด๋ฏธ์ง๋ฅผ ์ ์ฒด ํฌ๊ธฐ๋ก ๋ณด๊ธฐ

๋ค์ดํฐ๋ธ ์์ ์ฟ ํค ๊ณ์ธต(Mermaid)
๋ณด๊ธฐ์ ์ข์์ โ ํ์ง๋ง HTTPCookie๋, ๊ทธ ์์ฑ๋ค๋ Codable์ ์ค์ํ์ง ์์ฃ โฆ
๋ชจ๋ ์๋ ค์ง ์ฟ ํค ์์ฑ์ ์ ์ฅํ๋ Codable ๋ํผ๋ฅผ ๋ง๋ค ์๋ ์์ง๋ง, HTTPCookiePropertyKey๊ฐ ํ ๋น๋์ง ์์ ์์ฑ(์: HttpOnly)๋ ์์ด ์ค์ํ ์์ฑ์ ๋์น ์ ์์ต๋๋ค.
HTTPCookie์ ์์ฑ์ ๋ค์๊ณผ ๊ฐ์ด ์ ์๋ฉ๋๋ค:
[HTTPCookiePropertyKey: Any]?
์ฌ๊ธฐ์ HTTPCookiePropertyKey๋ String ๋ณ์นญ์ด์ง๋ง, Any๋ ๋ฌด์์ด๋ ๋ ์ ์์ต๋๋ค. ์ด์ ํ์
์์ ์ฑ ๋ฌธ์ ๊ฐ ์๊น๋๋ค.
Any๋ฅผ ์ฌ์ฉํ๋ฉด ๋์
๋๋ฆฌ์ ์ ์ฅ๋ ๊ฐ์ ํ์
์ ๋ณด๊ฐ ์ฌ๋ผ์ง๋๋ค. ์ฌ์ฉ์ ๊ธฐ๋ณธ(UserDefaults)์์ ๊ฐ์ ์ฝ์ด์ฌ ๋ ์ค์ ํ์
์ ์๊ธฐ ์ด๋ ต์ต๋๋ค. ์ ์ ํ ํ์
์์ ์ฑ์ด ์์ผ๋ฉด, ์๊ธฐ์น ์์ ํ์
์ผ๋ก ์ธํด ๋ฐํ์ ์๋ฌ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
๋คํํ NSKeyedArchiver๋ ์ฐ๋ฆฌ๊ฐ ์ฟ ํค ์์ฑ์์ ๋ง์ฃผ์น๋ ํ์ ๋ค์ ์ง์ํฉ๋๋ค. ํ์ ์์ ์ฑ์ ๋ํ ๋ถํ์ค์ฑ์ด ์กฐ๊ธ ๋จ๊ธด ํ์ง๋ง, ์ค๋ฌด์์๋ ์ถฉ๋ถํ ๋์ํ์ต๋๋ค.
๊ทธ ์ ์ JSONSerialization์ ์๋ํ๋๋ฐ, ํ์ ์์ธ๋ก ์คํจํ์ด์โฆ
์ํฐ๋ฅผ ๋๋ฅด๊ฑฐ๋ ํด๋ฆญํ์ฌ ์ด๋ฏธ์ง๋ฅผ ์ ์ฒด ํฌ๊ธฐ๋ก ๋ณด๊ธฐ

์ง๋ ฌํ ๋ฉ์๋๊ฐ ์ง์ํ๋ ๋ฐ์ดํฐ ํ์ ๋ค(Gemini)
๋ชจ๋ ๋ฌธ์ ์ ๋ฟ๋ฆฌ๋ ์ฌ์ค์ ๋ ๊ฐ์ ๋ ๋ฆฝ ํด๋ผ์ด์ธํธ โ ์ฑ๊ณผ ์ฑ ์์ WebView ์ปจํ ์คํธ โ ๊ฐ ๋์ผํ ์ธ์ ์ ๊ณต์ ํ๋ค๋ ์ ์ ๋๋ค.
์ค์๊ฐ์์ผ๋ก WKWebView๊ฐ ๋ฌด์์๋ก ์ฟ ํค๋ฅผ ์์ด๋ฒ๋ฆฌ๊ธฐ๋ ํฉ๋๋ค!
์ธ์ ๋๊ธฐํ๋ ์๊ฐ๋ณด๋ค ํจ์ฌ ์ด๋ ค์ ๊ณ , ์ฌ๊ธฐ ์ ์ํ ํด๋ฒ์๋ ๋ถ๊ตฌํ๊ณ ๋ค์ดํฐ๋ธ์ WKWebView๊ฐ ๋์์ ์ฟ ํค๋ฅผ ์ฝ๊ณ /์ฐ๋ ์๊ฐ ๋ ์ด์ค ์ปจ๋์ ์ด ๋ฐ์ํ ์ ์๋ ๊ฒฝ์ฐ๊ฐ โ๋ง์ต๋๋คโ. ๊ฒ๋ค๊ฐ WKWebView๊ฐ ์ฟ ํค๋ฅผ ์๋ ๋์ฐธ์ฌ๊ฐ ์ผ์ด๋๊ธฐ๋ ํ์ฃ !
iOS๋ WKWebView ๋์์ ๋งค์ฐ ์ ํ์ ์ผ๋ก๋ง ์ ์ดํ๊ฒ ํด ์ฃผ๋ฏ๋ก, ํฅํ ๊ฐ์ ์ ์ง์ผ๋ณด๋ ค ํฉ๋๋ค.
๊ณผ๊ฑฐ UIWebView๋ ์ฑ ๋ ๋ฒจ ์ฟ ํค ์ ์ฅ์(HTTPCookieStorage)๋ฅผ NSURLSession/NSURLConnection ๊ฐ์ ๋คํธ์ํฌ API์ ๊ณต์ ํ๊ธฐ์ ํจ์ฌ ๋ค๋ฃจ๊ธฐ ์ฌ์ ์ต๋๋ค.
WKHTTPCookieStore๊ฐ ์ด๋ ๊ฒ ๋ค๋ฅด๊ฒ ๋์ํด ๊ด๋ฆฌ๊ฐ ํจ์ฌ ์ด๋ ค์ด ์ด์ ๋ ์ฌ๋ฌ ๊ฐ์ง๊ฐ ์๊ฒ ์ง๋ง, ์ถ๊ฐ ๋ณด์์ ์ํด WebView๋ฅผ ๋ค์ดํฐ๋ธ ์์ฒญ์ ๋ฏผ๊ฐํ ๋ฐ์ดํฐ๋ก๋ถํฐ ์๋๋ฐ์ค ์ฒ๋ฆฌํ๊ฑฐ๋, WKWebView์ ๋ด๋ถ ๊ตฌ์กฐ์ ๊ธฐ๋ณธ ์ ์ฒด ์๋ ๋๊ธฐํ๊ฐ ๋ถ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์ผ ์ ์์ต๋๋ค.
OLX Group Acting iOS Staff Engineer Ivan Lisovyi์ ํผ๋๋ฐฑ
์ ๋ OLX iOS ์ฑ์์ ๋ค์ํ ์ธ์ฆ ๊ด๋ จ ๋ฌธ์ ๋ฅผ ๋ช ๋ฌ ๋์ ๊ณ ์ณค์ต๋๋ค. ์ฑ์ด ์๋ฌด๊ฒ๋ ํ์ง ์์๋๋ฐ๋ ์ฃผ๊ธฐ์ ์ผ๋ก ์ฟ ํค๊ฐ ์ง์์ง๋ ๋ฌธ์ ๋ ์์์ฃ . ์ฐ์ฐ์ ์ผ์น๋ค์! ๐
TL;DR: ๊ธ ๋ง๋ฏธ์ ์ง์ ํ์ จ๋ฏ ์ฟ ํค๋ ์ ๋ขฐํ๊ธฐ ์ด๋ ต๊ณ , WKWebView๊ฐ ๋ฌด์ธ๊ฐ ํด ์ฃผ๊ธฐ๋ฅผ ์ ์ ๋ก ํ ๋ก์ง์ ์ทจ์ฝํฉ๋๋ค. ๊ทธ๋์ ์ ํฌ๋ ๊ฒฐ๊ตญ ์ธ์ฆ ํ๋ก๋ฅผ ๊ฑฐ์ ์ฒ์๋ถํฐ ๋ค์ ์ผ์ต๋๋ค. ์ด ๋ฌธ์ ์ ์ข์ ํด๊ฒฐ์ฑ ์ด ์๋์ง๋ ์ ๋ชจ๋ฅด๊ฒ ๋ค์.
์ฐ๋ฆฌ๊ฐ ์ง๋ฉดํ ๊ธฐ๋ณธ ๋ฌธ์ ๋ ๋ค์ดํฐ๋ธ ์ฑ๊ณผ ์ฑ ๋ด๋ถ WebView๊ฐ ์ค์ง์ ์ผ๋ก ๋ ๋ฆฝ ํด๋ผ์ด์ธํธ์ฒ๋ผ ํ๋ํ๋ค๋ ์ ์ ๋๋ค.
๋ค์ดํฐ๋ธ์ WebView์ ๋ ์ธ์ ์ฒ๋ฆฌ๋ฅผ ๋๊ธฐํํ๋ ค ์ ์ฐ๊ธฐ๋ณด๋ค, โ์ธ์ ํ๋ก์โ๋ฅผ ์ฌ์ฉํด ๊ฐ ํด๋ผ์ด์ธํธ์ฉ ์ธ์ ์ ๋ณต์ ยท๊ฒฉ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๊ณ ๋ คํ ์ ์์ต๋๋ค.
๊ทธ๊ฒ ๋ง์ด ๋ผ์? ๋ชจ๋ ์น ์์ฒญ์ ํ๋ก์๋ก ๋ฆฌ๋ค์ด๋ ํธํด์ผ ํ๊ณ , ๊ทธ๋ฐ ํ๋ก์๊ฐ ํ์ํ๋ค๋ ๋๋ฌด ๊ณผํ๋ฐ์!
๋ง๋ ๋ง์์ ๋๋คโฆ ๊ทธ๋๋ ์ต์ ์ ํ๋์ด๊ธด ํ์ฃ โฆ
์ฐ๋ฆฌ ๋ด๋ถ ์ธ์ ๊ด๋ฆฌ์์๋ WebView ์์์ ์๋ฐ์คํฌ๋ฆฝํธ๋ก ์ธ์ ์ ๊ฐฑ์ ํ ์ ์์ต๋๋ค.
WebView ๋ด๋ถ์ ๊ฐฑ์ ํธ์ถ๊ณผ ๋ค์ดํฐ๋ธ ํธ์ถ ๊ฐ ๋ ์ด์ค ์ปจ๋์ ์ ํผํ๊ณ , ๋ค์ดํฐ๋ธ ์์ ๊ณ์ธต์ผ๋ก WKWebViewDataStorage์ ์ฟ ํค ์ ์ค์ ๋ฐฉ์งํ๊ธฐ ์ํด, ์ธ์ ๊ด๋ฆฌ์ ์ ์ด๊ถ์ ๋ค์ดํฐ๋ธ ์ฑ์ผ๋ก ๋๊ธธ ์ ์์ต๋๋ค.
์ฆ, WebView ์์์ ์คํ๋๋ ์น ํ์ด์ง๊ฐ ์ปจํ ์คํธ๋ฅผ ์ธ์งํด WKScriptMessageHandlerWithReply๋ก ๋ค์ดํฐ๋ธ ์ฑ์ ์ธ์ ๊ฐฑ์ ์ ์์ฒญํ๋๋ก ํฉ๋๋ค.
๋ค์ดํฐ๋ธ ์ฑ์ WebView ์คํฌ๋ฆฝํธ๋ก๋ถํฐ ํธ๋ฆฌ๊ฑฐ๋์ด ์ธ์ ๊ฐฑ์ ์ ์ํํ๊ณ , WebView๊ฐ ๋ค๋น๊ฒ์ด์ ์ ์คํํ๊ธฐ โ์ด์ โ์ ๊ฐฑ์ ๋ ์ธ์ ์ฟ ํค๋ฅผ WebView์ ์ธ์ ์ ์ฅ์์ ์จ ๋ฃ์ต๋๋ค. ๋ค๋น๊ฒ์ด์ ์ HTTP ํค๋๋ก ์ฟ ํค๋ฅผ ๋ณ๊ฒฝํ ์๋ ์์ผ๋๊น์.
Paul, ์ ๊น๋ง์โฆ
๊ทธ๋ฅ ์ฟ ํค๋ง ๋ณต์ฌํ๋ฉด, ์ญ์ ๋ ์ฟ ํค๋ ๋์น์์์. ์คํ ์ด์์ ๋ณต์ฌํ๋ฉด ์ญ์ ์ ๋ณด๋ ์ป์ง ๋ชปํ๋๊น์!
์ ์ฅ! ๋ง์์!
๊ทธ๋์ ๋ค์ดํฐ๋ธ ์์ฒญ ํ์๋ ์๋ต์ set-cookie ํค๋๋ฅผ ํ์ฑํด HTTPCookie๋ก ๋ง๋ค๊ณ , ๊ณผ๊ฑฐ ๋ ์ง ๋ง๋ฃ ์ค์ ๋ฑ์ผ๋ก ํํ๋ โ์ญ์ โ ์ ๋ณด๊น์ง ํฌํจ๋ HTTPCookie๋ค์ WKCookieStore์ ์ ๋ฌํด์ผ ํฉ๋๋ค.
์ํฐ๋ฅผ ๋๋ฅด๊ฑฐ๋ ํด๋ฆญํ์ฌ ์ด๋ฏธ์ง๋ฅผ ์ ์ฒด ํฌ๊ธฐ๋ก ๋ณด๊ธฐ

์๋ฐ์คํฌ๋ฆฝํธ๊ฐ ํธ๋ฆฌ๊ฑฐํ๊ณ ๋ค์ดํฐ๋ธ๊ฐ ์ ์ดํ๋ ์ธ์ /๋ฆฌํ๋ ์(Mermaid)
๊ฑฐ๊ธฐ์ ๋ํด, ์ฌ๋(ํน์ SDK) ์ค์๊น์ง ๋์ฑ ์์ ํ ๋ง์ผ๋ ค๋ฉด ๋ฆฌํ๋ ์ ํ ํฐ์ ์์ WebView์ ์ ๋ฌํ์ง ์๋ ๋ฐฉ๋ฒ๋ ์์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด WebView๊ฐ ๋ค์ดํฐ๋ธ ์ธ์ ์ ๋ง๊ฐ๋จ๋ฆด ์ ์์ต๋๋ค.
๋ง์ง๋ง์ผ๋ก, ์ฑ์ ํตํฉ๋ ๋ค๋ฅธ SDK๊ฐ HTTPCookieStorage๋ WKCookieStore์ ๊ณต์ ์คํ ๋ฆฌ์ง๋ฅผ ๊ฑด๋๋ฆด์ง ํ์ ํ ์ ์์ผ๋, WKWebView์ ๊ฐ์ธ ๋ธ๋ผ์ฐ์ง๊ณผ ๋ค์ดํฐ๋ธ ํธ์ถ์ ephemeral ๊ตฌ์ฑ์ ์ฌ์ฉํด ๊ณต์ ์ ์ฅ์๋ก๋ถํฐ ๊ฒฉ๋ฆฌํฉ๋๋ค.
์ด๊ฑด ๋ํฌ์๊ฒ ํตํ๊ฒ ์ง๋ง, ์ฐ๋ฆฌ์๊ฒ ์๋ ์๋!
๊ทธ๋ด ์๋ ์์ฃ . ๊ทธ ๊ฒฝ์ฐ์ ์ฌ๋ฌ๋ถ ํ๊ฒฝ์ ๋ง๋ ๋ง์ถค ํด๋ฒ์ ์ฐพ์์ผ ํฉ๋๋คโฆ
๋ค์ดํฐ๋ธ์ WebView ์ธ์ ์ ๋๊ธฐํํ๋ ๋ฐฉ๋ฒ์ ๊ดํ ์ด ๊ธ์ด ๋์์ด ๋์๊ธธ ๋ฐ๋๋๋ค. ์ง๋ฌธ์ด ์๊ฑฐ๋(๋ ์ข๊ฒ๋) ๋ ์ง์ ๊ฐ๋ฅํ ํด๋ฒ์ ์ฐพ์ผ์ จ๋ค๋ฉด ๊ผญ ์ฐ๋ฝ ์ฃผ์ธ์!
iOS๋ ํ๋ค์์ง๋ง, Android์์๋ ๋ง๋ฅ ์ฌ์ด ์ผ์ ์๋์์ต๋๋คโฆ
Android๋ ์ฑ์ ์์ํ ๋, ์ด์ ์ฑ ํ๋ก์ธ์ค๊ฐ ์ค์ ๋ก ์ข ๋ฃ๋๋์ง ๋ณด์ฅํ ์ ์์ต๋๋ค. ๋ ธ๋ ฅํ๊ธด ํ๊ณ Android 11+์์ ๋ ๋ ธ๋ ฅํ์ง๋ง, ์ปค๋์์ ์ธํฐ๋ฝํธ ๋ถ๊ฐ ์๋ฉด(uninterruptible sleep)์ด๋ ๋ฌดํ ๋ฃจํ์ ๋น ์ง ์ค๋ ๋(๋๊ฐ ๋๋ฐ์ด์ค ๋๋ผ์ด๋ฒ ๋ฒ๊ทธ๋ก ์ธํด)๋ผ๋ ํ ๊ฐ์ง ๊ฒฝ์ฐ๋ ํด๊ฒฐํ ์ ์์ต๋๋ค. ๊ทธ ์ค๋ ๋๋ SIGKILL๋ก๋ ์ข ๋ฃ๋์ง ์์ผ๋ฏ๋ก, ๊ทธ ์ค๋ ๋๊ฐ ์ํ ํ๋ก์ธ์ค๋ ์์ํ ๋จ์ ์ด๋ ค ์๋ ํ์ผ ๋์คํฌ๋ฆฝํฐ์ ํด๋น ์๋ฌธ ์ ๊ธ(advisory locks)์ ๊ณ์ ๋ถ์ก๊ณ ์๊ฒ ๋ฉ๋๋ค. :(
์ด๋ ์ผ๋ฐ์ ์ผ๋ก ๋งค์ฐ ๋๋ฌผ์ง๋ง, ๊ท ๋ฑํ๊ฒ ๋ถํฌ๋์ด ์์ง ์์ต๋๋ค. ์ค๋ ๋๋ฅผ ๋ฉ์ถ๊ฒ ํ๋ ๋ฒ๊ทธ๋ ๋๋ฐ์ด์ค/OS ๋น๋์ ๋ฐ๋ผ ๋ฌ๋ผ์, โํ๊ท ์ โ ์ฌ์ฉ์์๊ฒ๋ ๊ฒฐ์ฝ ์ผ์ด๋์ง ์๋๋ผ๋, ํด๋น ๋ฒ๊ทธ๊ฐ ์๋ ๋๋ฐ์ด์ค๋ฅผ ์ฐ๋ ๋ถ์ดํ ์ฌ์ฉ์(๊ทธ๋ฆฌ๊ณ ๊ทธ ์ฌ์ฉ์๋ค์ ๊ฐ์ง ๋ถ์ดํ ์ฑ ๊ฐ๋ฐ์)์๊ฒ๋ ๋ ๋์ ์ํ์ผ ์ ์์ต๋๋ค. ๋ํ ์ด๋ค ์ฑ์ ํน์ ์ํฉ์์ ์ด ๋ฒ๊ทธ๋ฅผ โ์ ๋ฐโํ๊ธฐ๋ ํ์ฌ, ๊ทธ ์ฑ์๊ฒ ํนํ ์ ์ข์ ์ ์์ต๋๋ค.