Flutter 개발

Flutter 딥링킹 완벽 가이드 3편: 딥링크 처리 서비스 구현

체계적이고 안전한 딥링크 처리 시스템 설계

2025년 9월 20일
10분 읽기
Flutter 딥링킹 완벽 가이드 3편: 딥링크 처리 서비스 구현

딥링크 처리의 실제 문제들

딥링크 처리는 단순히 URL을 받아서 화면으로 이동하는 것 이상의 복잡한 작업입니다. 실무에서 마주치는 다양한 상황들을 살펴보겠습니다.

딥링크 처리 시 고려사항:

1. 앱의 상태에 따른 처리
앱이 종료된 상태, 백그라운드 상태, 포그라운드 상태에 따라 딥링크 처리 방식이 달라져야 합니다. 특히 앱이 종료된 상태에서 딥링크로 시작될 때는 초기화 과정을 거쳐야 하므로 타이밍 이슈가 발생할 수 있습니다.

2. 인증 상태 확인
딥링크로 접근하려는 화면이 로그인이 필요한 경우, 사용자를 먼저 로그인 화면으로 보낸 후 원래 목적지로 이동시켜야 합니다. 이때 원래 딥링크 정보를 잃지 않도록 관리해야 합니다.

3. 데이터 로딩과 동기화
딥링크가 특정 콘텐츠를 가리킬 때, 해당 데이터가 로컬에 없을 수 있습니다. 서버에서 데이터를 먼저 가져온 후 화면을 표시해야 하는 경우가 많습니다.

4. 보안과 검증
악의적인 딥링크로부터 앱을 보호해야 합니다. URL 패턴 검증, 파라미터 sanitization, 권한 체크 등이 필수적입니다.

5. 에러 처리와 폴백
잘못된 딥링크, 삭제된 콘텐츠, 권한 없는 접근 등 다양한 에러 상황을 우아하게 처리해야 합니다.

딥링크의 수명 주기 이해

딥링크가 앱에 도달하고 처리되는 전체 과정을 이해하는 것이 중요합니다.

딥링크 수명 주기:

1단계: 링크 트리거
- 사용자가 외부 앱(브라우저, 메시지, 이메일 등)에서 링크 클릭
- 푸시 노티피케이션 탭
- QR 코드 스캔
- 음성 어시스턴트 명령

2단계: OS 라우팅
- OS가 링크를 분석하여 처리할 앱 결정
- 커스텀 스킴: 등록된 앱 직접 실행
- 유니버설 링크: 도메인 연결 확인 후 앱 또는 웹으로 라우팅

3단계: 앱 진입점
- Cold Start (앱 종료 상태): main() 함수부터 시작, 초기 딥링크 처리
- Warm Start (백그라운드): 기존 상태 유지, 새 딥링크 처리
- Hot Start (포그라운드): 현재 화면에서 새 화면으로 전환

4단계: 링크 파싱과 검증
- URL 구조 분해 (스킴, 호스트, 경로, 파라미터)
- 패턴 매칭으로 라우트 결정
- 보안 규칙 적용

5단계: 상태 체크와 준비
- 인증 상태 확인
- 필요한 데이터 로딩
- 권한 검증

6단계: 네비게이션 실행
- 목적지 화면으로 이동
- 애니메이션과 전환 효과 적용
- 히스토리 스택 관리

7단계: 사후 처리
- 분석 이벤트 전송
- 딥링크 소스 추적
- 캐시 업데이트

기본 딥링크 서비스 구현

먼저 uni_links 패키지를 사용한 기본적인 딥링크 서비스를 구현해보겠습니다.
Dart
// pubspec.yaml
dependencies:
  uni_links: ^0.5.1
  go_router: ^12.0.0

// services/deeplink_service.dart
import 'package:uni_links/uni_links.dart';
import 'dart:async';

class DeepLinkService {
  static final DeepLinkService _instance = DeepLinkService._internal();
  factory DeepLinkService() => _instance;
  DeepLinkService._internal();

  StreamSubscription? _linkSubscription;
  final _linkController = StreamController<String>.broadcast();
  
  Stream<String> get linkStream => _linkController.stream;

  Future<void> initialize() async {
    // 앱이 종료된 상태에서 딥링크로 시작된 경우
    try {
      final initialLink = await getInitialLink();
      if (initialLink != null) {
        _handleDeepLink(initialLink);
      }
    } catch (e) {
      print('초기 딥링크 처리 실패: $e');
    }

    // 앱이 실행 중일 때 딥링크를 받는 경우
    _linkSubscription = linkStream.listen(
      _handleDeepLink,
      onError: (err) => print('딥링크 수신 오류: $err'),
    );
  }

  void _handleDeepLink(String link) {
    print('딥링크 수신: $link');
    _linkController.add(link);
  }

  void dispose() {
    _linkSubscription?.cancel();
    _linkController.close();
  }
}

딥링크 파싱과 라우팅 전략

받은 딥링크를 분석하고 적절한 화면으로 라우팅하는 것은 딥링크 처리의 핵심입니다. 다양한 URL 패턴을 처리할 수 있는 유연한 시스템이 필요합니다.

URL 구조 분석:

딥링크 URL은 다음과 같은 구성 요소로 이루어집니다:
- Scheme: 프로토콜 (https, myapp 등)
- Host: 도메인 또는 액션 식별자
- Path: 리소스 경로
- Query Parameters: 추가 정보
- Fragment: 앵커 또는 추가 식별자

예시: myapp://product/123?source=email&campaign=summer#reviews
- Scheme: myapp
- Host: product
- Path: /123
- Query: source=email&campaign=summer
- Fragment: reviews

라우팅 전략 패턴:

1. 직접 매핑 (Direct Mapping)
URL 경로를 앱 라우트와 1:1로 매핑합니다. 가장 간단하지만 유연성이 떨어집니다.

2. 패턴 매칭 (Pattern Matching)
정규표현식이나 와일드카드를 사용하여 동적 경로를 처리합니다. 복잡한 URL 구조에 적합합니다.

3. 핸들러 체인 (Handler Chain)
여러 핸들러를 순서대로 실행하여 URL을 처리합니다. 각 핸들러는 특정 패턴을 담당합니다.

4. 라우트 테이블 (Route Table)
URL 패턴과 처리 함수를 매핑한 테이블을 관리합니다. 확장성이 좋고 관리가 용이합니다.
Dart
// models/deeplink_model.dart
class DeepLinkData {
  final Uri uri;
  final String scheme;
  final String? host;
  final String path;
  final Map<String, String> queryParams;
  final String? fragment;

  DeepLinkData({
    required this.uri,
    required this.scheme,
    this.host,
    required this.path,
    required this.queryParams,
    this.fragment,
  });

  factory DeepLinkData.fromUrl(String url) {
    final uri = Uri.parse(url);
    return DeepLinkData(
      uri: uri,
      scheme: uri.scheme,
      host: uri.host.isEmpty ? null : uri.host,
      path: uri.path,
      queryParams: uri.queryParameters,
      fragment: uri.fragment.isEmpty ? null : uri.fragment,
    );
  }
}

// services/deeplink_router.dart
class DeepLinkRouter {
  final GoRouter _goRouter;
  
  DeepLinkRouter(this._goRouter);

  void route(DeepLinkData data) {
    // 경로 기반 라우팅
    if (data.path.startsWith('/product/')) {
      final productId = data.path.split('/').last;
      _goRouter.go('/product/$productId');
    } 
    else if (data.path.startsWith('/user/')) {
      final userId = data.path.split('/').last;
      _goRouter.go('/user/$userId');
    }
    else if (data.path == '/home') {
      _goRouter.go('/');
    }
    else {
      // 알 수 없는 경로는 홈으로
      _goRouter.go('/');
    }
  }
}

보안과 검증 구현

딥링크는 외부에서 앱으로 진입하는 통로이므로 보안이 매우 중요합니다. 악의적인 공격을 방지하기 위한 여러 보안 계층을 구현해야 합니다.

보안 위협 유형:

1. URL 인젝션 공격
악의적인 파라미터를 삽입하여 앱의 동작을 조작하려는 시도입니다. SQL 인젝션과 유사한 개념으로, 파라미터 값을 그대로 신뢰해서는 안 됩니다.

2. 권한 우회 시도
인증이 필요한 화면에 직접 접근하려는 시도입니다. 항상 현재 사용자의 권한을 확인해야 합니다.

3. 피싱과 스푸핑
정상적인 딥링크처럼 위장한 악성 링크입니다. 신뢰할 수 있는 도메인과 패턴만 허용해야 합니다.

4. 리소스 고갈 공격
대량의 딥링크 요청으로 앱을 마비시키려는 시도입니다. Rate limiting과 큐 관리가 필요합니다.

보안 검증 체크리스트:
- 허용된 스킴과 호스트인지 확인
- URL 패턴이 예상된 형식인지 검증
- 파라미터 값의 타입과 범위 검증
- 특수 문자와 인코딩 처리
- 사용자 권한과 인증 상태 확인
- 요청 빈도 제한
- 로깅과 모니터링
Dart
// services/deeplink_validator.dart
class DeepLinkValidator {
  // 허용된 스킴
  static const allowedSchemes = ['https', 'myapp'];
  
  // 허용된 호스트 (유니버설 링크용)
  static const allowedHosts = [
    'myapp.com',
    'www.myapp.com',
    'link.myapp.com',
  ];
  
  // 허용된 경로 패턴
  static final allowedPaths = [
    RegExp(r'^/home$'),
    RegExp(r'^/product/[a-zA-Z0-9-]+$'),
    RegExp(r'^/user/[a-zA-Z0-9_]+$'),
    RegExp(r'^/invite/[A-Z0-9]{6,10}$'),
  ];

  static bool isValid(String url) {
    try {
      final uri = Uri.parse(url);
      
      // 1. 스킴 검증
      if (!allowedSchemes.contains(uri.scheme)) {
        print('허용되지 않은 스킴: ${uri.scheme}');
        return false;
      }
      
      // 2. 호스트 검증 (HTTPS인 경우)
      if (uri.scheme == 'https' && 
          !allowedHosts.contains(uri.host)) {
        print('허용되지 않은 호스트: ${uri.host}');
        return false;
      }
      
      // 3. 경로 패턴 검증
      final isValidPath = allowedPaths.any(
        (pattern) => pattern.hasMatch(uri.path)
      );
      
      if (!isValidPath) {
        print('허용되지 않은 경로: ${uri.path}');
        return false;
      }
      
      // 4. 파라미터 검증
      return _validateParameters(uri.queryParameters);
      
    } catch (e) {
      print('URL 파싱 오류: $e');
      return false;
    }
  }
  
  static bool _validateParameters(Map<String, String> params) {
    // 파라미터 값 길이 제한
    const maxValueLength = 200;
    
    for (final entry in params.entries) {
      if (entry.value.length > maxValueLength) {
        return false;
      }
      
      // SQL 인젝션 방지
      if (_containsSqlKeywords(entry.value)) {
        return false;
      }
      
      // XSS 방지
      if (_containsHtmlTags(entry.value)) {
        return false;
      }
    }
    
    return true;
  }
  
  static bool _containsSqlKeywords(String value) {
    final sql = value.toLowerCase();
    final keywords = [
      'select', 'insert', 'update', 'delete',
      'drop', 'union', 'exec', 'script'
    ];
    return keywords.any((k) => sql.contains(k));
  }
  
  static bool _containsHtmlTags(String value) {
    final htmlPattern = RegExp(r'<[^>]*>');
    return htmlPattern.hasMatch(value);
  }
}

에러 처리와 복구 전략

딥링크 처리 중 발생할 수 있는 다양한 에러 상황을 우아하게 처리하는 것이 중요합니다. 사용자 경험을 해치지 않으면서도 명확한 피드백을 제공해야 합니다.

일반적인 에러 시나리오:

1. 콘텐츠를 찾을 수 없음
링크가 가리키는 콘텐츠가 삭제되었거나 존재하지 않는 경우입니다. 이때는 유사한 콘텐츠를 제안하거나 검색 기능을 제공할 수 있습니다.

2. 권한 부족
사용자가 해당 콘텐츠에 접근할 권한이 없는 경우입니다. 권한 획득 방법을 안내하거나 대체 콘텐츠를 제공해야 합니다.

3. 네트워크 오류
데이터를 로드하는 중 네트워크 문제가 발생한 경우입니다. 재시도 옵션과 오프라인 모드를 제공할 수 있습니다.

4. 앱 버전 불일치
구버전 앱에서 신버전 기능의 딥링크를 열려고 하는 경우입니다. 업데이트 안내가 필요합니다.

복구 전략:
- Graceful Degradation: 최선의 대안을 제공
- Progressive Enhancement: 기본 기능부터 점진적 로딩
- Fallback Chain: 여러 단계의 대체 옵션 제공
- User Guidance: 명확한 에러 메시지와 해결 방법 안내
Dart
// services/deeplink_error_handler.dart
enum DeepLinkError {
  invalidUrl,
  contentNotFound,
  unauthorized,
  networkError,
  versionMismatch,
  unknown,
}

class DeepLinkErrorHandler {
  final GoRouter router;
  
  DeepLinkErrorHandler(this.router);
  
  void handle(DeepLinkError error, {String? context}) {
    switch (error) {
      case DeepLinkError.invalidUrl:
        _showErrorAndRedirect(
          '잘못된 링크',
          '요청하신 링크가 올바르지 않습니다.',
          '/',
        );
        break;
        
      case DeepLinkError.contentNotFound:
        _showErrorAndRedirect(
          '콘텐츠를 찾을 수 없음',
          '요청하신 콘텐츠가 삭제되었거나 이동되었습니다.',
          '/search',
        );
        break;
        
      case DeepLinkError.unauthorized:
        // 로그인 페이지로 리다이렉트
        router.go('/login?redirect=$context');
        break;
        
      case DeepLinkError.networkError:
        _showRetryDialog(
          '네트워크 오류',
          '데이터를 불러올 수 없습니다.',
          context,
        );
        break;
        
      case DeepLinkError.versionMismatch:
        _showUpdateDialog();
        break;
        
      case DeepLinkError.unknown:
      default:
        _showErrorAndRedirect(
          '오류 발생',
          '요청을 처리할 수 없습니다.',
          '/',
        );
    }
  }
  
  void _showErrorAndRedirect(
    String title,
    String message,
    String fallbackRoute,
  ) {
    // 에러 메시지 표시 후 대체 경로로 이동
    // 실제 구현에서는 Dialog나 SnackBar 사용
    router.go(fallbackRoute);
  }
  
  void _showRetryDialog(
    String title,
    String message,
    String? retryContext,
  ) {
    // 재시도 옵션 제공
  }
  
  void _showUpdateDialog() {
    // 앱 업데이트 안내
  }
}

딥링크 분석과 추적

딥링크의 효과를 측정하고 개선하기 위해서는 체계적인 분석이 필요합니다.

추적해야 할 핵심 지표:

1. 유입 경로 (Source Attribution)
- 딥링크가 어디서 왔는지 (이메일, SMS, 소셜미디어, QR코드 등)
- UTM 파라미터를 활용한 캠페인 추적
- 리퍼러(Referrer) 정보 수집

2. 전환율 (Conversion Rate)
- 딥링크 클릭 대비 목표 달성 비율
- 각 경로별 전환율 비교
- 이탈 지점 분석

3. 사용자 행동 (User Behavior)
- 딥링크 이후 사용자 경로
- 체류 시간과 인터랙션
- 재방문율과 리텐션

4. 기술적 성능 (Technical Performance)
- 딥링크 처리 시간
- 실패율과 에러 유형
- 플랫폼별 성공률

이러한 데이터를 수집하여 딥링크 전략을 지속적으로 개선할 수 있습니다.

다음 단계

딥링크 처리 서비스의 핵심 구성 요소들을 살펴보았습니다. 수명 주기 이해, 파싱과 라우팅, 보안 검증, 에러 처리, 분석까지 실무에서 필요한 모든 측면을 다뤘습니다.

핵심 포인트:
- 딥링크는 단순한 URL 처리가 아닌 복잡한 시스템입니다
- 보안 검증은 선택이 아닌 필수입니다
- 에러 처리는 사용자 경험의 핵심입니다
- 분석을 통해 지속적으로 개선해야 합니다

시리즈 구성:
- 1편: 기초 개념과 플랫폼 설정
- 2편: GoRouter 고급 라우터 관리
- 3편: 딥링크 처리 서비스 구현 (현재)
- 4편: 라우터 상태 관리와 분석
- 5편: 실전 통합 구현

다음 편에서는 Riverpod을 활용한 라우터 상태 관리와 분석 시스템 구축을 다루겠습니다.
#딥링킹
#딥링크서비스
#링크처리
#보안검증
#uni_links