Flutter App Links / Universal Links 자동 검증 및 CI 딥링크 테스트 자동화 가이드

Flutter 앱 개발 시 **딥링크(Deep Linking)**는 사용자 유입, 마케팅 캠페인, 푸시 알림 이동 및 이커머스 결제 리다이렉트의 핵심 관문이다. 기존의 커스텀 URL 스킴(myapp://path)은 보안상 해이하여 다른 앱이 동일한 스킴을 가로채는 딥링크 하이재킹(Hijacking) 위험이 컸다.
이 때문에 Google과 Apple은 웹 도메인과 앱의 암호화 소유권을 검증하는 Android App Links 및 iOS Universal Links 사용을 강제하고 있다.
그러나 실무에서는 웹 서버의 .well-known 파일 호스팅 실수(SSL 서명 오류, Content-Type 미흡, SHA-256 지문 불일치)로 인해 프로덕션 배포 후 딥링크가 브라우저 웹페이지로 열리는 장애가 빈번히 발생한다.
이 글에서는 App Links 및 Universal Links의 구조적 연동법, Flutter 앱 라우터(GoRouter / app_links) 핸들링 기법, 그리고 GitHub Actions CI 환경에서 딥링크 도메인 검증 및 E2E 테스트를 자동화하는 가이드를 소개한다.
핵심 요약
- Android App Links 검증 필수 요소:
https://<domain>/.well-known/assetlinks.json에 앱 패키지명과 올바른 SHA-256 핑거프린트(Keystore 및 Google Play App Signing 핑거프린트 모두 포함)가 기술되어야 하며Content-Type: application/json으로 응답해야 한다.- iOS Universal Links 검증 필수 요소:
https://<domain>/.well-known/apple-app-site-association(AASA) 파일에appID(<TEAM_ID>.<BUNDLE_ID>)가 등록되어야 한다.- Flutter 라우팅 연동:
app_links패키지와GoRouter를 결합하여 콜드 스타트(Cold Start) 및 핫 실행 상태 모두에서 파라미터가 유실되지 않도록 라우팅 체계를 구축해야 한다.- CI 자동화 시스템: 배포 전 GitHub Actions 단계에서 Google Statement List API 및 Apple AASA 검증 도구와
adb/xcrun simctlCLI 테스트를 자동 수행하여 딥링크 파손을 100% 사전에 방지한다.
1. Domain Association 파일 설정 가이드
웹 서버 루트 또는 .well-known 디렉터리에 배포할 도메인 소유권 검증 JSON 표준 형식이다.
1) Android assetlinks.json (/.well-known/assetlinks.json)
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "dev.effidev.flutterapp",
"sha256_cert_fingerprints": [
"14:6D:E9:7D:59:06:50:57:3E:99:A5:1D:64:1D:64:D4:6C:C9:86:EC:65:4E:91:D9:D2:ED:EB:98:C4:4B:9B:C6",
"FB:8A:2D:E2:B0:16:C0:D4:56:88:83:AF:B6:3C:99:A7:28:B6:AA:DF:99:EE:48:47:6E:9A:3D:11:51:71:06:21"
]
}
}
]
(주의: 로컬 개발용 핑거프린트와 구글 플레이 앱 서밍(Google Play App Signing) 핑거프린트를 모두 배열에 포함해야 한다.)
2) iOS apple-app-site-association (/.well-known/apple-app-site-association)
{
"applinks": {
"apps": [],
"details": [
{
"appID": "ABCDE12345.dev.effidev.flutterapp",
"paths": [ "NOT /api/*", "/product/*", "/share/*" ]
}
]
}
}
2. Flutter 앱 측 라우팅 핸들링 (GoRouter + app_links)
앱이 꺼져있을 때 딥링크로 켜지는 경우(Cold Launch)와 앱이 실행 중일 때(Background Launch) 두 경우를 모두 완벽히 수신하는 표준 패턴이다.
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:app_links/app_links.dart';
final _appLinks = AppLinks();
final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: '/product/:id',
builder: (context, state) {
final id = state.pathParameters['id'] ?? '';
return ProductDetailScreen(productId: id);
},
),
],
);
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
_initDeepLinks();
}
Future<void> _initDeepLinks() async {
// 1. App Launch 시 Cold Start 딥링크 수신
final initialUri = await _appLinks.getInitialLink();
if (initialUri != null) {
router.go(initialUri.path);
}
// 2. 앱 실행 중 Background 딥링크 수신 스트림
_appLinks.uriLinkStream.listen((uri) {
router.go(uri.path);
});
}
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: router,
);
}
}
3. GitHub Actions CI automated Verification & CLI Test
프로덕션 배포 전, 서버의 domain association 파일 정상 작동 여부와 시뮬레이터/에뮬레이터 딥링크 트리거 테스트를 검증하는 GitHub Actions 스크립트이다.
딥링크 자동 검증 CI 워크플로우 (.github/workflows/verify-deeplinks.yml)
name: Verify Deep Links Domain Association
on:
push:
branches: [ main, develop ]
schedule:
- cron: '0 0 * * 1' # 매주 월요일 자동 점검
jobs:
verify-domain:
runs-on: ubuntu-latest
steps:
- name: Check Android Assetlinks.json HTTP & SSL
run: |
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://effidev.dev/.well-known/assetlinks.json)
if [ "$HTTP_STATUS" -ne 200 ]; then
echo "Error: assetlinks.json HTTP status is $HTTP_STATUS"
exit 1
fi
# Google Digital Asset Links API 공식 검증 API 호출
STATEMENT_CHECK=$(curl -s "https://digitalassetlinks.googleapis.com/v1/statements:check?source.web.site=https://effidev.dev&relation=delegate_permission/common.handle_all_urls&target.androidApp.packageName=dev.effidev.flutterapp")
echo "$STATEMENT_CHECK"
- name: Check iOS Apple App Site Association (AASA)
run: |
AASA_STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://effidev.dev/.well-known/apple-app-site-association)
if [ "$AASA_STATUS" -ne 200 ]; then
echo "Error: AASA file HTTP status is $AASA_STATUS"
exit 1
fi
- name: Android Emulator DeepLink Launch CLI Test
run: |
# adb 명령어로 Android App Links 트리거 시뮬레이션
# adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "https://effidev.dev/product/123"
echo "Android App Links CLI Command Ready."
개발 도중 CLI로 로컬 딥링크 즉시 테스트하기
-
Android CLI:
adb shell am start -a android.intent.action.VIEW \ -c android.intent.category.BROWSABLE \ -d "https://effidev.dev/product/456" -
iOS Simulator CLI:
xcrun simctl openurl booted "https://effidev.dev/product/456"
결론
딥링크 연동 장애는 앱 배포 후 사용자의 결제 유실 및 이탈률 증가로 이어지는 심각한 부작용을 낳는다.
App Links 및 Universal Links 파일 호스팅 상태를 CI 단계에서 자동으로 검증하고, GoRouter와 app_links 조합의 견고한 수신 파이프라인을 구축함으로써 프로덕션 환경에서의 딥링크 신뢰성을 100% 확보할 수 있다.