스타웍스 개발기 (3) - 사파리 익스텐션과 웹킷 인젝션

간단한 개인 프로젝트 개발 과정을 글로 남기고 있습니다. 스타벅스에서 WiFi 연결하는 과정을 편리하게 해주는 macOS 앱을 만들고 있습니다. 제게 필요한 앱이라서 공부하는 겸 만들고 있는데, 사실 같은 문제를 해결한 “사파리 확장 프로그램”과 “macOS용 앱”이 이미 나와 있습니다. 그래서 아주 새로운 내용은 아닙니다만, 대신 개발 과정을 상세히 설명하면서 영상과 글로 남기며 진행하고 있으므로, 관련한 공부를 함께 즐기시는 재미는 있으리라 기대해요.

이 프로젝트에 대한 자세한 설명은 앞선 글을 참고해주세요.

스타벅스 WiFi 연결 과정에 끼어들자

이 연재의 (2)에서 동영상으로 스타벅스에서 WiFi에 연결할 때의 내용을 설명드렸습니다. 요약하자면, 스타벅스의 WiFi AP에 붙으면, 처음에는 인터넷 통신이 안 되다가, 스타벅스가 원하는 웹페이지(http://first.wifi.olleh.com/으로 시작하는 웹페이지)에서 약관 동의 절차를 거치고 나면, 그 당일 인터넷 접근이 가능하다는 내용이었어요.

Captive Network — 반공개 네트워크

처음에 정상적인 인터넷 통신이 안 되는 상태를 Captive Network에 있다고 얘기하는데요, 이 상태에서는 특정 웹사이트에만 접근할 수 있습니다. 일반 TCP 통신은 아예 연결되지 않고, 그중 HTTP 통신의 경우에는 스타벅스 약관 동의에 관련된 웹페이지에만 접근할 수 있습니다. 그 외의 HTTP 접근은, 스타벅스 AP가 가로채고는 가짜 응답을 대신 내보내는데, 이 가짜 응답이 302 Redirect응답이며, 이 응답으로 인해, 우리 브라우저는 약관 동의 페이지로 자동 이동하게 됩니다.

글로 적으니, 참 복잡해 보입니다만, (2)편의 영상을 참고해주시면 쉽게 이해가 되실 거라 기대합니다.

macOS 기본 연결러 — Captive Network Assistant.app

이런 방식의 (공개도 아니고 비공개도 아닌) 반공개 네트워크가 꽤 널리 쓰여서인지, 운영체제 차원에서도 이 과정을 지원하며, 브라우저처럼 생긴 별도의 앱을 띄워서 대신 처리해 줍니다. macOS에는 Captive Network Assistant.app라는 이름의 앱이 이 일을 하며, 마치 사파리 브라우저처럼 생겼지만, 매번 깨끗한 상태에서 시작한다는 차이가 있습니다. 쿠키/세션이 남지 않으며, 사파리 익스텐션과도 따로 놀기에 별로 할 수 있는 일이 없습니다.

그래서 우선 이 Captive Network Assistant.app가 뜨는 상황을 피해야 하는데, 그러기 위해서 터미널을 열어 시스템 옵션을 하나 꺼주면 됩니다.

$ sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.captive.control Active -boolean false

이처럼 옵션을 끄면, 더 이상 WiFi 연결 과정에서 Captive Network Assistant.app이 관여하지 않습니다. 이 옵션을 끄는 방식으로, 기존에 나와있는 사파리/크롬 확장 프로그램과 macOS용 앱이 작동합니다. 두 형태 모두 우선 이 옵션을 꺼야 합니다.

절차가 조금 복잡해 보입니다만, 일단 이 과정으로 사파리 확장 프로그램이든 macOS용 앱이든 그다음 어떤 처리를 대신할 수 있게 되는 거지요.

참고로, 이 옵션을 껐을 때의 단점이 있습니다. 자동으로 연결과정을 보여주지 않기 때문에, 웹브라우저를 열어서 인증 절차를 거치기 전에는 인터넷 통신이 되지 않고, 왜 안 되는지 알 수도 없습니다. Mail.app 등의 웹브라우저가 아닌 앱들의 통신이 먹통이 되는 거지요. 이 문제를 해결하는 방법은 앱 개발이 끝나갈 즈음에 다시 말씀드릴게요.

이 글도 스타벅스에서 쓰고 있습니다. 저부터 WiFi연결이 번거로워서 앱을 빨리 만들고 싶네요

사파리(크롬) 확장 프로그램이 하는 일

우선 사파리나 크롬까지 제어권이 이동하면, 그때부터는 확장 프로그램의 영역입니다. 사파리나 크롬 등의 웹브라우저 확장 프로그램 (extension, 익스텐션)이 할 수 있는 일은, 어떤 웹사이트에 접근했을 때, 별도로 준비한 자바스크립트(.js) 코드나 스타일시트(.css)를 첨부하는 것입니다. 우리가 원하는 확장 프로그램은 스타일시트(CSS)와는 전혀 상관없고, 자바스크립트 코드를 조금 덧붙이면 될 것 같습니다.

크롬의 경우는 어떤지 확인하지 않았습니다만, 사파리의 경우, 덧붙인 js코드로 특정 사이트의 DOM에는 접근할 수 있는데, 자바스크립트 문맥(context)은 달라서 원래 웹페이지에 있는 자바스크립트 함수나 변수에는 접근할 수 없습니다. 그래서 별도의 자바스크립트 컨텍스트에서 목표로 한 특정 웹사이트의 DOM엘리먼트들을 건드려서 원하는 처리를 하게 됩니다. (이하, 어차피 큰 차이는 없기에 사파리 기준으로 설명함)

그럼, 자동 로그인을 위해서 확장 프로그램으로 할 수 있는 일은 무엇이냐?!

  1. 스타벅스 와이파이 연결용 웹서버에 접근할 때, 미리 준비한 자바스크립트 코드를 추가로 읽게 합니다.
  2. 미리 준비한 자바스크립트 코드는, (2–1) 최초 동의 과정에서 사용자가 입력하는 값을 어딘가에 기억합니다. (2–2) 이미 기억한 값이 있다면, 약관 동의 페이지가 떴을 때, input 필드 값들을 기본으로 채워주고, 나아가 동의 버튼까지 대신 눌러주도록 합니다.

우선, first.wifi.olleh.com에 접근했을 때, 우리의 커스텀 자바스크립트 파일을 추가로 로드하게 하고, (이건 확장 프로그램 속성으로 설정합니다). 그 자바스크립트 파일은 기본적으로,

document.addEventListener("DOMContentLoaded", function() {
// 여기서 원하는 작업을 진행합니다. 그러면 원본 HTML 구성요소가 준비되고 나서,
// 여기의 코드가 실행되지요.
});

이렇게 onload 콜백을 걸어 둡니다. jQuery 등의 라이브러리가 필요하다면, 함께 로드해서 쓸 수도 있지만, 아주 간단한 기본 코드로 모든 일을 처리할 수 있고, 오직 사파리만 지원하면 되므로, 그러지 않아도 될 것 같습니다.

한 가지 여담으로, 이제 사파리 확장 프로그램을 앱스토어에서 받을 수 있게 됐다는 점인데요, 보통의 앱을 설치하듯이, 애플 앱스토어에서 확장 프로그램을 내포한 앱을 설치하면, 사파리의 확장 프로그램 영역에 보이게 됩니다. 앱스토어에서 받는 것이므로, 엄격한(?) 심사를 통과한 것이기에 이 확장 프로그램이 불필요한 문제를 일으키진 않을까 하는 걱정에서 조금 벗어날 수 있지 않을까 합니다. 이번 프로젝트처럼 별도의 처리가 필요 없는 “사파리 확장 프로그램”을 만든다면, 새 포맷으로 배포하면 사용자에게도 편리할 것 같습니다.

익스텐션으로 처리하는 방식은 우선 여기까지만 설명하고, 다음 편부터 그 내부 진행과정을 더 상세하게 적도록 할게요.

사파리 익스텐션에 대한 내용은 아래 애플 개발자 사이트에 잘 정리돼 있습니다.

https://developer.apple.com/library/content/documentation/Tools/Conceptual/SafariExtensionGuide/ExtensionsOverview/ExtensionsOverview.html

macOS용 앱이 할 일

이 일을 네이티브 애플리케이션의 형태라면 어떻게 처리할까요? (2.5)편에서 적은 대로, 종욱님으로부터 별도 앱이 나와 있다는 소식을 들었지만, 자세히 살펴보지는 않았습니다. 이미 구현된 형태와 다를 수도 있고, 비슷할 수도 있습니다. 우선은 제 나름대로 구현해서 공개할 예정입니다.

macOS용 앱의 형태로 할 수 있는 일은 기본적으로, 원래 Captive Network 상황에서 뜨는 Captive Network Support.app이 하는 일을 대신하면 됩니다. WiFi 상태에 따라 인터페이스가 UP 상태이고, 통신이 가능해 보인다면, http://captive.apple.com/hotspot_detect.html에 접근해 보는 거죠.

이때 200 OK가 떨어지면 정상 인터넷 통신이 된다고 보고, 더 이상 할 일은 없습니다. 그러나 만약 302 Redirect가 돌아오면, 현재 Captive Network 안에 있다고 판단해서, (1) 사파리를 띄워 약관 동의 절차를 거치게 유도하거나, (2) 아니면 자체적으로 앱에 포함된 브라우저를 띄워도 됩니다. macOS 앱에서는 WebKit 프레임워크를 쓰면 앱 안에 웹브라우저를 넣을 수 있습니다. 사파리도 내부적으로는 이 웹킷 엔진을 쓰기 때문에 거의 동일한 화면이 보이지요.

(1) 사파리를 띄운다면, 사파리에 동의 절차를 편하게 처리해주는 확장 프로그램 (익스텐션)을 미리 설치해둬서 그 과정을 편하게 진행해 주면 되고, (2) 자체 브라우저로 한다면, 확장 프로그램으로 할 일을 앱 내에서 진행할 수 있습니다. 확장 프로그램과 사파리가 통신하는 방식과는 미묘하기 다르지만, 큰 차이는 없습니다. 저는 자체 브라우저에 자바스크립트 코드 덧붙여서 처리하도록 하겠습니다.

자체 브라우저를 넣는 방법은 매우 간단해서 놀라울 정도입니다. 대강의 코드를 적자면, 이렇습니다.

import WebKit

class OurAwesomeViewContorller {
var webView: WKWebView!

override func viewDidLoad() {
super.viewDidLoad()
let conf = WKWebViewConfiguration()
// conf를 어루만져서, 각종 설정을 준비. js코드를 첨부하는 것도 여기
// ...
webView = WKWebView(frame: .zero, configuration: conf)
self.view = webView // 현재 뷰컨트롤러의 뷰을 웹뷰로 교체
}

// 그 외 우아한 처리들...
}

코드가 iOS나 macOS에서 개발할 때 쓰는 스위프트(swift)언어로 작성한 거라서 그 모습은 생소하시겠지만, 생각보다는 간단하죠? WKWebView라는 인스턴스 만들어서 붙이면 끝입니다. 세상에, 이렇게 쉬울 수가?!

웹킷 프레임워크에 대한 문서는 아래 링크에서 자세히 살펴볼 수 있습니다.

다시 정리하면, macOS 앱으로,

  1. WiFi 인터페이스가 켜지면, (특히 SSID가 KT_starbucks라면)
  2. 자체적으로 Captive Network상황인지 판단
  3. (만약 YES) 자체 브라우저를 띄워서 “사파리 확장 프로그램”이 하는 일을 대신 진행
  4. (만약 NO) 더 이상 할 일 없음. 이후 WiFi 인터페이스 상태가 바뀔 때까지 잠자기

이렇게 진행하면 될 것 같습니다. 글로만 길게 설명드렸기에, 어느 정도 전달이 됐을지 궁금하네요. 궁금한 점 있으시거든 편하게 질문해주세요.

읽어주셔서 고맙습니다.

Like what you read? Give 김대현 a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.