iOS WKWebView 내에서 Universal Links를 활성화하려면 어떻게 해야 하나요? Universal Links는 WKWebView 내부의 적격 링크에서 이미 해석될 수 있습니다. WKNavigationDelegate를 구현하면 호스트 앱이 라우팅 정책을 커스텀하여 앱 소유 목적지를 가로채고, decidePolicyForNavigationAction을 통해 외부 핸드오프를 제어하며, 프레임 수준의 보안을 강화할 수 있습니다.
iOS 앱 아키텍처에서 WKWebView 탐색 가로채기(interception)를 사용하면 호스트 앱이 Universal Links, 사용자 지정 URL 스킴 및 웹 목적지 전반에 걸쳐 라우팅 정책을 맞춤 설정할 수 있습니다. WKNavigationDelegate 내에서 탐색 요청 메타데이터를 평가함으로써 앱은 자체 목적지를 내부적으로 라우팅하거나, 외부 타겟을 시스템 핸들러로 위임하고, 프레임 수준의 보안 정책을 강제할 수 있습니다.
| 용어 | 정의 | 관련 엔티티 | 검색 의도 역할 |
|---|---|---|---|
| WebView | iOS 앱 내에서 대화형 웹 콘텐츠를 렌더링하는 내장된 WebKit 기반 뷰 컴포넌트입니다. | iOS SDK | 정보 제공 / 상업적 |
| Universal Links | 검증된 웹 도메인과 네이티브 iOS 앱 뷰를 연결하는 표준 HTTPS 메커니즘입니다. | 딥링크 라우팅 | 기술적 / 정보 제공 |
| Custom URL Scheme | URL을 네이티브 앱으로 라우팅하기 위해 앱이 정의한 URI 스킴입니다. | 모바일 딥링크 | 정보 제공 |
iOS에서 WKWebView와 Universal Link 라우팅이 상호작용하는 방식

WebKit 탐색 생명주기 및 앱 소유 라우팅 정책
Apple은 Safari 및 WKWebView 환경 전반에서 지원되는 시스템 수준의 라우팅 메커니즘으로 Universal Links를 구현합니다. 사용자가 내장된 WKWebView 안에서 적격 링크를 탭하면, 플랫폼은 도메인 연결을 확인하고 운영 체제 정책에 따라 실행 경로를 지정할 수 있습니다.
시스템이 인식하는 Universal Links가 네이티브 핸들러로 실행을 넘길 수 있지만, 내장된 브라우저 환경에서는 종종 앱별 라우팅 로직이 필요합니다. 예를 들어 링크가 호스트 앱 자체 도메인을 대상으로 하는 경우, 개발자는 외부 앱을 완전히 재실행하는 대신 네이티브 뷰 컨트롤러를 통해 직접 이동하는 것을 선호합니다. WKNavigationDelegate를 구현하면 호스트 앱이 링크 평가에 대한 세밀한 제어 권한을 갖게 되어, 팀이 커스텀 허용 목록을 강제하고 내부 목적지를 예측 가능하게 라우팅할 수 있습니다.
사용자 경험의 장벽: 인앱 웹 브라우징이 웹 리디렉션 루프를 유발할 때
내장된 WebView는 프로모션 마이크로사이트, 고객 센터, 파트너 카탈로그 및 마케팅 랜딩 페이지를 호스팅하기 위해 앱 내에서 자주 배포됩니다. 인앱 웹 페이지에 사용자를 호스트 앱의 다른 섹션으로 이동시키거나(예: '앱에서 보기' 버튼) 파트너 앱으로 이동시키려는 링크가 포함되어 있으면, 기본 탐색 설정이 중복 렌더링을 유발할 수 있습니다:
- 중복된 웹 렌더링: 네이티브 뷰 컨트롤러를 렌더링하는 대신, 인앱 페이지의 반응형 웹 버전이 표시되어 반복적인 인증이 필요하게 되고 시각적 일관성이 저하됩니다.
- 웹 트랩(Web Trapping): 사용자가 주요 네이티브 앱 인터페이스로 돌아갈 직관적인 방법 없이 깊은 웹 탐색 스택 내에 갇힐 수 있습니다.
- 앱 간 전환 실패: 제3자 서비스(내비게이션 앱, 소셜 공유 대화상자, 결제 게이트웨이 등)를 가리키는 링크를 탭할 경우, 해당 서비스가 사용자 지정 URL 스킴을 사용하는 경우 명시적인 위임이 필요합니다.
웹-투-앱 핸드오프를 위한 WKWebView와 SFSafariViewController 비교
iOS에서 인앱 웹 브라우징을 설계할 때 엔지니어링 팀은 WKWebView와 SFSafariViewController 중에서 선택해야 합니다:
SFSafariViewController: 자동 완성 및 콘텐츠 차단과 같은 기능을 갖춘, 시스템이 관리하는 독립형 Safari 브라우징 인터페이스를 제공합니다. 호스트 앱은 브라우징 활동이나 웹사이트 데이터를 검사할 수 없으며 UI 커스텀은 틴트 색상으로 제한됩니다.WKWebView: 웹 콘텐츠를 별도의 WebKit 프로세스에서 실행하면서 앱의 UI 프로세스 내에 호스팅되는 내장형 뷰 컴포넌트입니다. 심층적인 UI 커스텀, JavaScript 브리지 및 커스텀 레이아웃 통합이 가능하며, 라우팅 정책을 맞춤 설정하고 앱 정의 커스텀 스킴을 처리하기 위해WKNavigationDelegate를 명시적으로 구현해야 합니다.
decidePolicyForNavigationAction이 WebKit 라우팅을 가로채는 방법
탐색 정책 파이프라인: WKNavigationAction, request, decisionHandler 이해하기
WKWebView 내의 탐색 흐름을 제어하기 위해 개발자는 Apple의 WKNavigationDelegate 개발자 가이드를 준수하는 커스텀 델리게이트를 할당합니다. 주요 가로채기 지점은 다음 델리게이트 메서드입니다:
func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
)
사용자 상호작용, 프로그래밍 방식의 리디렉션 또는 양식 제출에 의해 트리거된 탐색 작업은 이 메서드를 통과합니다. WKNavigationAction 객체는 다음과 같은 주요 메타데이터를 제공합니다:
navigationAction.request.url: 요청 중인 대상URL.navigationAction.navigationType: 트리거 유형 (.linkActivated,.other,.formSubmitted).navigationAction.sourceFrame: 탐색 요청을 시작한 프레임에 관한 정보.navigationAction.targetFrame: 콘텐츠가 로드될 예정인 대상 프레임에 관한 정보.
decisionHandler는 WebKit에 요청된 탐색을 허용할지 취소할지 알리는 완료 클로저입니다.
.allow와 .cancel 반환 시점: WebKit 리소스 로딩 생명주기 제어
decisionHandler에 전달되는 WKNavigationActionPolicy는 WebKit이 탐색을 계속할지 여부를 제어합니다:
.allow: 웹 뷰 내에서 요청된 탐색을 계속하도록 WebKit에 알립니다..cancel: WebKit에 요청된 탐색을 취소하도록 지시합니다. 이 정책은 호스트 앱이 사용자 지정 스킴을 가로채거나, 앱 소유의 Universal Link를 내부적으로 라우팅하거나, 외부 타겟을UIApplication.shared.open()으로 위임할 때 실행됩니다.
decisionHandler는 탐색 정책 결정이 지연 없이 진행되도록 각 탐색 작업당 정확히 한 번 호출되어야 합니다.
탐색 유형 평가: 사용자 클릭(.linkActivated)과 자동 리디렉션 구분
WKNavigationAction.navigationType을 사용하면 개발자가 명시적인 사용자 상호작용과 자동화된 스크립트를 구분할 수 있습니다:
.linkActivated: 사용자가 HTML 앵커 태그(<a href="...">)를 물리적으로 탭함..other:window.location.href업데이트, 메타 새로고침 또는 초기webView.load()호출과 같은 프로그래밍 방식의 탐색을 나타냄..formSubmitted/.formResubmitted: 양식 POST 또는 GET 제출을 나타냄.
navigationType을 평가하면 앱 수준의 보수적이고 명시적인 링크 정책을 강제할 수 있습니다. 외부 사용자 지정 스킴 호출이나 제3자 앱 핸드오프의 경우, .linkActivated를 정책 게이트로 요구하면 의도하지 않은 백그라운드 스크립트가 자동화된 외부 앱 실행을 트리거하는 것을 방지하는 데 도움이 됩니다.
보류 주기(Retain Cycle) 없이 비동기 결정 정책 처리하기
라우팅 검증이나 권한 확인을 위해 로컬 캐시 또는 보안 검증기를 쿼리해야 하는 경우:
- 에러 및 가드 조건을 포함한 모든 실행 경로에서
decisionHandler가 실행되도록 보장합니다. WKWebView, 델리게이트, 상위UIViewController사이의 보류 주기를 방지하기 위해 이스케이핑 클로저 내에서 약한 참조([weak self])를 사용합니다.
호스트 앱 연결 도메인과 외부 Universal Links 구분하기

앱 소유 목적지와 시스템 수준 링크 위임 관리
앱 소유 경로의 경우 중복된 Universal Link 조회를 통해 동일한 앱으로 다시 진입하려는 시도를 피하십시오. 앱 소유 목적지는 호스트 앱의 내부 라우터를 통해 직접 처리하고, 현재 앱을 떠나야 하거나 외부 서비스에 의해 해결되어야 하는 목적지에 대해서만 시스템 열기 기능을 사용하십시오.
이러한 아키텍처적 분리는 원활한 탐색을 보장합니다:
- 호스트 앱 연결 도메인: URL 호스트가 호스트 앱의 자체 연결 도메인(
app.example.com)과 일치하면, 웹 뷰 탐색을 취소(decisionHandler(.cancel))하고 경로를 검증한 다음, 파싱된 파라미터를 앱의 내부 내비게이션 라우터로 직접 전달하십시오. - 외부 앱: URL이 외부 파트너 목적지나 허용된 사용자 지정 스킴을 가리키는 경우, 앱 수준의 명시적 링크 게이트(
navigationType == .linkActivated)를 적용하고 웹 뷰 탐색을 취소한 뒤,UIApplication.shared.open(url)로 요청을 전달하여 운영 체제가 외부 앱을 실행하도록 하십시오.
내부 경로 검증 아키텍처: AppRouteValidator를 통한 경로 및 쿼리 파라미터 추출
들어오는 URL이 호스트 앱의 연결 도메인과 일치하면, 뷰 컨트롤러 전환을 트리거하기 전에 URL 문자열이 엄격한 라우팅 검증기를 통과해야 합니다.
AppRouteValidator 모델의 역할:
- 지원되는 내부 경로 허용 목록(예:
/open/,/product/,/promo/,/checkout/)에 대해 URL 경로를 검증합니다. - 쿼리 파라미터(예:
id,promo,utm_source)를 추출합니다. - 문자 집합 제한, 길이 경계, 중복 키 거부를 강제하고 정리된
ValidatedAppRoute데이터 구조를 반환합니다.
시스템 UIApplication 위임을 통한 외부 제3자 Universal Links 처리

WKWebView 내의 웹 페이지가 외부 서비스(파트너 앱, 소셜 플랫폼, 외부 유틸리티 등)로 연결될 때, 호스트 앱은 iOS 시스템으로 라우팅을 위임할 수 있습니다:
let options: [UIApplication.OpenExternalURLOptionsKey: Any] = [
.universalLinksOnly: true
]
UIApplication.shared.open(url, options: options) { success in
if !success {
// 앱 정책 폴백: Universal Link를 처리할 네이티브 앱이 없는 경우 웹 목적지 로드
}
}
앱 정책으로 .universalLinksOnly를 사용하면, Universal Link를 처리하도록 확인된 네이티브 앱이 설치되어 있는 경우에만 사용자가 현재 앱 밖으로 전환되도록 보장할 수 있습니다.
HTTPS Universal Links와 함께 사용자 지정 URL 스킴 폴백(myapp://) 관리
HTTPS Universal Links가 iOS에서의 표준 딥링크를 나타내지만, 앱 정의 커스텀 스킴(myapp:// 또는 partnerapp://)은 프로모션 캠페인 및 파트너 통합 전반에서 여전히 흔히 사용됩니다.
통합 WKNavigationDelegate 구현에서:
- HTTP/HTTPS가 아닌 스킴을 먼저 검사합니다. 스킴이 허용된 커스텀 프로토콜과 일치하고 명시적 링크 정책(
navigationType == .linkActivated)을 만족하면, 델리게이트가UIApplication.shared.open()으로 발송하기 전에 호스트, 경로, 파라미터를 검증합니다. - 인식되지 않는 스킴이나 의도하지 않은 백그라운드 스킴 호출은 즉시 취소하여, 처리되지 않은 탐색 에러나 스크립트 기반의 의도치 않은 호출 범람을 방지합니다.
[사용자가 iOS WKWebView 내의 링크와 상호작용]
│
▼
[WKNavigationDelegate: decidePolicyForNavigationAction]
│
┌─────────────┴─────────────┐
▼ ▼
[!action.sourceFrame.isMainFrame] [action.sourceFrame.isMainFrame]
│ │
▼ ▼
[서브프레임 보안 게이트] [목적지 스킴 및 호스트 검사]
├─ HTTP(S) -> .allow │
└─ 비-웹 -> .cancel ┌───────────┼───────────┐
(서브프레임 억제) ▼ ▼ ▼
[호스트 도메인] [외부 웹] [사용자 지정 스킴]
│ │ │
▼ ▼ ▼
[앱 경로] [링크 확인] [링크 확인]
├─ 유효 -> ├─ 파트너-> ├─ 유효 & 클릭->
│ 내부 이동 │ 앱 실행 │ 앱 실행
└─ 무효 -> └─ 웹 -> └─ 무효/자동->
.cancel .allow .cancel
메인 프레임 보안 강제 및 Iframe 하이재킹 방지

내장 웹 탐색을 신뢰할 수 없는 입력으로 취급: OWASP 딥링크 보안 표준
부정확한 딥링크에 관한 OWASP 모바일 앱 보안 테스트 가이드 지침에 따라, 모바일 내비게이션 핸들러가 처리하는 모든 URL 및 파라미터 페이로드는 신뢰할 수 없는 외부 입력으로 취급되어야 합니다.
WKWebView 내에 렌더링된 웹 페이지는 제3자 스크립트, 광고 배너 또는 사용자 생성 콘텐츠를 로드할 수 있습니다. 탐색 델리게이트가 검증 없이 임의의 URL을 네이티브 뷰 컨트롤러나 UIApplication.shared.open()으로 전달하면, 예기치 않은 파라미터가 민감한 내부 앱 경로를 타겟팅할 수 있습니다.
내장 Iframe 및 새 창 타겟으로부터 메인 프레임 탐색 격리
WKNavigationAction에 관한 Apple 개발자 문서에 따라 프레임 보안을 평가하려면 시작 프레임을 확인해야 합니다:
sourceFrame.isMainFrame == true: 탐색이 1차 최상위 문서 프레임에 의해 직접 시작됨.sourceFrame.isMainFrame == false: 탐색이 내장된 서브프레임이나 iframe에 의해 시작됨.targetFrame == nil: 탐색이 새 창 타겟(예:target="_blank"가 있는 앵커)을 요청함.
Iframe 하이재킹(내장된 iframe이 백그라운드에서 외부 앱을 실행하거나 네이티브 뷰 전환을 트리거하려고 시도하는 경우)을 방지하려면 델리게이트가 sourceFrame.isMainFrame을 평가해야 합니다. 시작 프레임이 iframe(sourceFrame.isMainFrame == false)인 경우, 표준 HTTP/HTTPS 서브프레임 탐색은 허용(.allow)하되 웹이 아닌 사용자 지정 스킴이나 네이티브 라우팅 핸드오프는 차단(.cancel)하십시오.
악의적인 서브프레임 프로토콜 호출 및 백그라운드 스킴 범람 방지
시작 프레임 검사를 강제하면 서브프레임이 의도치 않은 외부 스킴 호출을 트리거하는 것을 방지할 수 있습니다:
if !navigationAction.sourceFrame.isMainFrame {
let scheme = url.scheme?.lowercased() ?? ""
if scheme == "http" || scheme == "https" {
decisionHandler(.allow) // 표준 HTTP(S) 서브프레임 탐색 허용
} else {
decisionHandler(.cancel) // 서브프레임에서 웹이 아닌 스킴 억제
}
return
}
클라이언트 라우팅에서 엄격한 경로 및 쿼리 파라미터 허용 목록 강제
내부 연결 도메인 URL과 외부 사용자 지정 스킴 모두 실행 전에 엄격한 검증기 모델을 통과해야 합니다:
- 경로 접두사 허용 목록(Allowlisting): 승인된 경로 접두사(예:
/open/,/product/,/promo/,/checkout/)를 강제하여 임의의 또는 잘못된 경로를 거부합니다. - 쿼리 키 필터링: 예기치 않은 쿼리 키를 삭제하고 중복 파라미터 키를 거부하여 파라미터 오염을 방지합니다.
- 데이터 유형 및 길이 제약: 파라미터 값을 영숫자 문자 집합으로 제한하고 최대 길이 경계(≤ 64자)를 강제합니다.
Swift를 활용한 프로덕션 수준의 WKNavigationDelegate 구현
Swift에서의 CustomWebViewController 및 델리게이트 아키텍처 구성
프로덕션용 WKWebView 컨트롤러는 웹 구성, 탐색 정책 평가, 내부 라우팅 및 외부 위임을 조정합니다. 이 구현은 검증 규칙을 전용 검증기 클래스(AppRouteValidator 및 CustomSchemeValidator) 내에 캡슐화하여 델리게이트 콜백을 깔끔하고 테스트 가능하며 안전하게 유지합니다.
AppRouteValidator 및 CustomSchemeValidator 모델 구현
검증기 모델은 엄격한 fail-closed 보안을 강제합니다:
AppRouteValidator는 내부 연결 도메인을 검증하고, 경로 접두사를 확인하며, 쿼리 파라미터를 구조화된ValidatedAppRoute객체로 위생 처리합니다.CustomSchemeValidator는 승인된 사용자 지정 스킴(myapp)을 확인하고, 허용된 호스트(open,product,event)를 검증하며, 쿼리 값을 위생 처리합니다.
Openinstall은 어트리뷰션 및 지연된 파라미터 복구를 위해 애플리케이션 소유 라우팅 계층과 함께 통합될 수 있습니다. 포괄적인 통합 가이드는 SDK 통합 문서를 검토하십시오.
아래 기술 구현은 Swift에서 안전한 WKNavigationDelegate를 구성하는 방법을 보여줍니다:
// iOS: 엄격한 WKNavigationDelegate 라우팅 및 프레임 보안이 포함된 CustomWebViewController
// 참조 통합 예제입니다. 배포된 아키텍처에 따라 메서드 시그니처와 도메인 매핑을 확인하십시오.
import UIKit
import WebKit
struct ValidatedAppRoute {
let path: String
let queryParams: [String: String]
}
// 1. 호스트 앱의 연결 도메인(내부 경로)을 위한 검증기
class AppRouteValidator {
private static let allowedPrefixes = ["/open/", "/product/", "/promo/", "/checkout/"]
private static let allowedQueryKeys = Set(["target", "id", "promo", "utm_source"])
static func validate(url: URL) -> ValidatedAppRoute? {
let path = url.path
// 승인된 경로 접두사 허용 목록 강제
guard allowedPrefixes.contains(where: { path.hasPrefix($0) }) else {
return nil
}
var sanitizedParams: [String: String] = [:]
var seenKeys = Set<String>()
if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems {
let validChars = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-")
for item in queryItems {
// Fail-closed 검증: 승인되지 않은 쿼리 키 또는 중복 키가 있으면 URL 거부
guard allowedQueryKeys.contains(item.name), !seenKeys.contains(item.name) else { return nil }
seenKeys.insert(item.name)
let value = item.value ?? ""
if value.count <= 64 && value.rangeOfCharacter(from: validChars.inverted) == nil {
sanitizedParams[item.name] = value
} else {
return nil
}
}
}
return ValidatedAppRoute(path: path, queryParams: sanitizedParams)
}
}
// 2. 외부 사용자 지정 스킴(myapp://)을 위한 검증기
class CustomSchemeValidator {
private static let allowedSchemes = Set(["myapp"])
private static let allowedHosts = Set(["open", "product", "event"])
private static let allowedPathPrefixes = ["/detail/", "/view/", "/main/"]
private static let allowedQueryKeys = Set(["target", "id", "promo", "utm_source"])
static func validate(url: URL) -> URL? {
guard let scheme = url.scheme?.lowercased(), allowedSchemes.contains(scheme) else {
return nil
}
guard let host = url.host?.lowercased(), allowedHosts.contains(host) else {
return nil
}
let path = url.path
if !path.isEmpty && !allowedPathPrefixes.contains(where: { path.hasPrefix($0) }) {
return nil
}
var seenKeys = Set<String>()
if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems {
let validChars = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-")
for item in queryItems {
guard allowedQueryKeys.contains(item.name), !seenKeys.contains(item.name) else { return nil }
seenKeys.insert(item.name)
let value = item.value ?? ""
if value.count > 64 || value.rangeOfCharacter(from: validChars.inverted) != nil {
return nil
}
}
}
return url
}
}
// 3. 안전한 탐색 정책 가로채기가 포함된 WKWebView 호스팅 UIViewController
class CustomWebViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
private let hostAssociatedDomain = "app.example.com"
private let allowedExternalPartnerHosts = Set(["partner.example.com"])
override func viewDidLoad() {
super.viewDidLoad()
let configuration = WKWebViewConfiguration()
webView = WKWebView(frame: view.bounds, configuration: configuration)
webView.navigationDelegate = self
view.addSubview(webView)
}
func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
) {
guard let url = navigationAction.request.url else {
decisionHandler(.allow)
return
}
// 보안 검사 1: Iframe 하이재킹을 방지하기 위해 시작 프레임 경계(sourceFrame) 강제
if !navigationAction.sourceFrame.isMainFrame {
let scheme = url.scheme?.lowercased() ?? ""
if scheme == "http" || scheme == "https" {
decisionHandler(.allow) // 표준 HTTP(S) 서브프레임 탐색 허용
} else {
decisionHandler(.cancel) // 서브프레임/iframe에서 비-웹 스킴 억제
}
return
}
let scheme = url.scheme?.lowercased() ?? ""
let isExplicitLinkActivation = (navigationAction.navigationType == .linkActivated)
// 보안 검사 2: 호스트 앱의 자체 연결 도메인 처리
// UIApplication.shared.open을 호출하는 대신 내부적으로 라우팅
if scheme == "https", let host = url.host?.lowercased(), host == hostAssociatedDomain {
if let validatedRoute = AppRouteValidator.validate(url: url) {
AppInternalRouter.shared.navigate(to: validatedRoute)
}
decisionHandler(.cancel) // 내부적으로 라우팅하기 위해 웹뷰 내 로딩 취소
return
}
// 보안 검사 3: 링크 활성화 정책을 통한 허용된 사용자 지정 스킴(myapp://) 처리
if scheme != "http" && scheme != "https" && scheme != "about" {
// 사용자 지정 스킴 외부 앱 실행은 명시적인 사용자 링크 활성화를 요구하도록 강제
if isExplicitLinkActivation, let validatedURL = CustomSchemeValidator.validate(url: url) {
UIApplication.shared.open(validatedURL, options: [:], completionHandler: nil)
}
decisionHandler(.cancel) // 처리되지 않은 스킴 에러를 방지하기 위해 웹뷰 내 로딩 취소
return
}
// 보안 검사 4: 외부 목적지 및 새 창(target="_blank") 요청 처리
if scheme == "http" || scheme == "https" {
let host = url.host?.lowercased() ?? ""
// 명시적 링크 정책으로 검증된 파트너 Universal Links를 외부 앱으로 위임
if allowedExternalPartnerHosts.contains(host) && isExplicitLinkActivation {
let options: [UIApplication.OpenExternalURLOptionsKey: Any] = [
.universalLinksOnly: true
]
UIApplication.shared.open(url, options: options) { [weak self] success in
if !success {
// 앱 정책 폴백: 네이티브 앱이 처리하지 않는 경우 웹 뷰 내에 외부 파트너 목적지 로드
guard let self = self else { return }
self.webView.load(navigationAction.request)
}
}
decisionHandler(.cancel)
return
}
// targetFrame이 nil인 경우(새 창 요청), 현재 webView에 안전하게 로드
if navigationAction.targetFrame == nil {
webView.load(navigationAction.request)
decisionHandler(.cancel)
return
}
// 표준 웹 콘텐츠는 WKWebView 내에서 로딩 계속
decisionHandler(.allow)
return
}
decisionHandler(.allow)
}
}
// 앱별 내부 라우터 플레이스홀더 (Openinstall SDK API 아님)
class AppInternalRouter {
static let shared = AppInternalRouter()
func navigate(to route: ValidatedAppRoute) {
// 경로와 쿼리 파라미터를 기반으로 내부 UI 뷰 컨트롤러 전환 실행
}
}
스레드 안전 실행: 메인 액터에서 UI 전환이 실행되도록 보장
현재 Swift 동시성 모델에서 WKNavigationDelegate 콜백은 메인 액터로 격리됩니다. 앱 라우팅과 뷰 컨트롤러 전환은 메인 액터에서 실행되어 네이티브 탐색 워크플로우 전반에 걸쳐 스레드 안전성을 유지합니다.
WKWebView 딥링크 탐색 에러 및 진단 매트릭스
포괄적인 iOS WKWebView 딥링크 문제 해결 가이드
아래 매트릭스는 iOS WKWebView 내에서 딥링크와 사용자 지정 스킴을 관리할 때 발생하는 공통적인 실패 모드와 주요 근본 원인, 권장되는 엔지니어링 수정 사항을 요약합니다:
| 에러 특징 / 증상 | 주요 근본 원인 | 적용 가능한 iOS 버전 | 진단 체크포인트 | 권장 수정 사항 |
|---|---|---|---|---|
| Universal Link가 웹에서 로드됨 | 앱 소유 도메인이 가로채지지 않음 | iOS 9+ | decidePolicyForNavigationAction 미처리 |
호스트 도메인을 가로채고, 경로를 파싱하여 내부적으로 라우팅하고, .cancel 처리 |
| 자체 도메인 링크 라우팅 실패 | 자체 도메인에서 UIApplication.open 호출 |
iOS 9+ | 자체 호스트에서 UIApplication.shared.open 호출됨 |
외부 자체 열기를 피하고 내부 라우터로 직접 라우팅 |
| 사용자 지정 스킴이 조용히 실패 | WebKit이 비-HTTP 프로토콜을 인식하지 못함 | iOS 9+ | 스킴이 UIApplication으로 위임되지 않음 |
델리게이트에서 스킴을 가로채고, 허용 목록을 검증한 뒤 UIApplication을 통해 열기 |
| Iframe 프로토콜 하이재킹 | 서브프레임이 외부 사용자 지정 스킴을 트리거 | iOS 9+ | sourceFrame.isMainFrame 미확인 |
if !sourceFrame.isMainFrame으로 가드하고 비-웹 스킴을 억제 |
| UI 핸드오프 경고 또는 전환 이슈 | 메인 스레드 밖에서 UI 전환 실행 | iOS 9+ | 메인 액터 디스패치 누락 | 내부 라우터와 뷰 컨트롤러 전환을 위한 메인 액터 실행 보장 |
자주 묻는 질문(FAQ)
개발자가 WKWebView에서 Universal Links를 가로채려면 어떻게 해야 하나요?
WKWebView에서 내 앱을 실행하기 위해 UIApplication.shared.open을 사용할 수 있나요?
WKWebView에 포함된 iframe이 외부 앱 실행을 트리거하는 것을 어떻게 방지하나요?
요약 및 결정 프레임워크
iOS WKWebView 내에서 Universal Links와 사용자 지정 스킴을 처리하려면 WebKit의 웹 렌더링 컨테이너와 네이티브 UIKit 탐색 생명주기 사이의 경계를 연결해야 합니다. 기본 탐색 정책에 의존하면 앱 소유의 라우팅 로직이 필요할 때 원활한 핸드오프를 방해할 수 있습니다.
프레임 경계를 검증하고, 외부 핸드오프에 대해 보수적인 링크 활성화 정책을 강제하며, 엄격한 경로 검증기를 통해 내부 연결 도메인을 파싱하고, UIApplication.shared.open으로 안전하게 외부 타겟을 위임하는 강력한 WKNavigationDelegate를 구현함으로써, 엔지니어링 팀은 iframe 프로토콜 하이재킹으로부터 보호하면서 제어된 탐색을 유지할 수 있습니다.
네이티브 iOS 딥링크 및 파라미터 라우팅 아키텍처를 살펴보려면 SDK 통합 문서를 참조하십시오.
관련 자료
-
개념: iOS WebView 라우팅, Universal Links 가로채기, WKNavigationDelegate, 프레임 경계 격리
-
기술: Apple WebKit, iOS UIKit, WKWebView, OpoInstall iOS SDK
-
표준: IETF RFC 3986 URI(Uniform Resource Identifier), Apple 연결 도메인 사양, OWASP 모바일 앱 보안 테스트 가이드(MASTG)
-
API:
WKNavigationDelegate.decidePolicyForNavigationAction,WKNavigationAction.sourceFrame,UIApplication.shared.open -
공식 문서 및 참조:
Share this article



