[Troubleshooting] 웹뷰(WebView) 환경에서 위치 정보(Geolocation)가 작동하지 않는 이유와 해결법

최정은·2026년 2월 16일

pin-plate

목록 보기
3/4

최근 개인 프로젝트인 'Pin-Plate(맛집 기록 서비스)'를 개발하며 웹으로 만든 기능을 모바일 앱 내 웹뷰로 이식하는 작업을 진행했습니다.

분명 웹 브라우저에서는 장소 검색 기능이 아주 잘 작동했는데, 유독 모바일 앱(안드로이드 웹뷰) 환경에서만 검색 결과가 빈 화면으로 나오는 현상을 발견했습니다. 원인은 보안 정책으로 인한 위치 정보(Geolocation) 요청 차단이었고, 이를 Native Bridge로 해결한 과정을 공유합니다.


1. 문제 상황: "웹은 되는데, 앱은 왜 안 될까?"

장소 검색 로직은 현재 사용자의 위경도 좌표를 받아와 Query String에 담아 API를 호출하는 방식입니다.

  • 웹(PC): http://localhost:3000 접속 시 위치 정보가 정상적으로 수집됨.
  • 앱(Mobile): 웹뷰를 통해 개발 서버 IP(http://192.168.x.x:3000)로 접속 시 위치 정보를 가져오지 못해 검색 결과가 0건으로 표시됨.

2. 원인 분석: 보안 컨텍스트(Secure Context)의 제한

W3C 명세와 브라우저 보안 정책에 따르면, Geolocation API는 개인정보 보호를 위해 Secure Context(보안 컨텍스트)에서만 작동합니다.

왜 localhost만 허용될까?

웹 표준에서는 HTTPS 환경을 안전하다고 판단합니다. 다만, 개발 편의를 위해 http://localhosthttp://127.0.0.1은 예외적으로 안전한 환경(Secure Context)으로 간주하여 위치 정보 요청을 허용합니다.

모바일 IP 접속의 한계

모바일 기기에서 PC 서버 IP로 접속하는 것은 브라우저 입장에서 '안전하지 않은 원격 접속'입니다. localhost 예외 조항에 해당하지 않으므로, HTTPS가 적용되지 않은 상태에서는 위치 정보 API 호출이 원천 차단됩니다.

참고: iOS 웹뷰는 시뮬레이터나 특정 조건에서 localhost 접속 시 관대할 수 있지만, 안드로이드 웹뷰는 HTTP 환경에서의 민감한 권한 요청에 훨씬 엄격합니다.


3. 첫 시도: HTTPS 적용

가장 먼저 Next.js의 --experimental-https 옵션을 사용하여 로컬 서버를 HTTPS로 구동해 보았습니다. 하지만 두 가지 문제로 인해 기각했습니다.

  1. SSL Trust 에러: 사설 인증서(Self-signed certificate)를 사용하다 보니 모바일 기기에서 "신뢰할 수 없는 인증 기관"이라며 접속을 차단(SSL error: The certificate authority is not trusted)했습니다.
  2. 설정의 복잡도: 안드로이드 onReceivedSslError를 통해 에러를 무시할 수 있지만, 이는 보안상 매우 취약하며 실제 배포 환경과는 거리가 먼 설정이라 판단했습니다.

4. 최종 해결책: Native Bridge 패턴 활용

웹뷰 자체의 navigator.geolocation 기능을 포기하고, 앱(Native)의 기능을 빌려오는 Bridge 패턴을 선택했습니다.

프로세스

  1. Web: 웹뷰 감지 시, Native로 위치 정보 요청 메시지를 전송합니다.
  2. App: expo-location 등을 이용해 기기 자체 GPS 좌표를 취득합니다.
  3. App → Web: 취득한 좌표를 웹뷰의 postMessage를 통해 웹으로 다시 전달합니다.
  4. Web: 전달받은 좌표를 사용하여 장소 검색 API를 실행합니다.

브릿지 통신은 크게 웹에서 요청하기앱에서 응답하기 두 단계로 나뉩니다.

1) Web (Next.js / TypeScript)

웹에서는 현재 환경이 웹뷰인지 확인한 후, 네이티브에 메시지를 보냅니다.

// types/webview.d.ts
interface Window {
  ReactNativeWebView?: {
    postMessage: (message: string) => void;
  };
}

// components/LocationSearch.tsx
import { useEffect } from 'react';

const requestLocationFromNative = () => {
  if (window.ReactNativeWebView) {
    // 네이티브 앱에 위치 정보 요청 메시지 전송
    window.ReactNativeWebView.postMessage(
      JSON.stringify({ type: 'GET_LOCATION' })
    );
  } else {
    console.warn("웹뷰 환경이 아닙니다.");
  }
};

2) App (React Native / Expo)

앱에서는 웹뷰의 onMessage를 통해 요청을 받고, expo-location으로 좌표를 구해 다시 전달합니다.

import React, { useRef } from 'react';
import { WebView, WebViewMessageEvent } from 'react-native-webview';
import * as Location from 'expo-location';

export default function App() {
  const webViewRef = useRef<WebView>(null);

  const onMessage = async (event: WebViewMessageEvent) => {
    const message = JSON.parse(event.nativeEvent.data);

    if (message.type === 'GET_LOCATION') {
      // 1. 위치 권한 요청
      const { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== 'granted') {
        console.log('위치 권한이 거부되었습니다.');
        return;
      }

      // 2. 현재 위치 가져오기
      const location = await Location.getCurrentPositionAsync({});
      const { latitude, longitude } = location.coords;

      // 3. 웹뷰로 좌표 전송
      const response = {
        type: 'SET_LOCATION',
        payload: { latitude, longitude },
      };
      
      webViewRef.current?.postMessage(JSON.stringify(response));
    }
  };

  return (
    <WebView
      ref={webViewRef}
      source={{ uri: 'http://your-local-ip:3000' }}
      onMessage={onMessage}
    />
  );
}

5. Expo 환경 설정 (app.json)

Native 기능을 사용하기 위해 OS별 권한 설정이 필요합니다. Expo 환경에서의 설정법입니다.

1) 플러그인 설정

expo-location 라이브러리가 필요한 설정을 자동으로 생성하도록 추가합니다.

"plugins": [
  [
    "expo-location",
    {
      "locationAlwaysAndWhenInUsePermission": "Allow $(PRODUCT_NAME) to use your location."
    }
  ]
]

2) iOS 설정 (ios.infoPlist)

애플의 심사 기준을 준수하기 위해 구체적인 목적을 명시해야 합니다.

"ios": {
  "infoPlist": {
    "NSLocationWhenInUseUsageDescription": "사용자 주변 맛집 검색 및 위치 기록을 위해 현재 위치 권한을 사용합니다."
  }
}

3) Android 설정 (android.permissions)

GPS와 기지국 기반 위치 정보를 모두 받기 위해 아래 권한들을 추가합니다.

"android": {
  "permissions": [
    "android.permission.ACCESS_COARSE_LOCATION",
    "android.permission.ACCESS_FINE_LOCATION"
  ]
}

마치며

웹뷰 개발은 웹 브라우저와 네이티브 OS 사이의 보안 정책 차이를 이해하는 과정인 것 같습니다. 단순히 웹을 앱 안에 띄우는 것에 그치지 않고, Native Bridge를 적극 활용할 때 훨씬 견고하고 안정적인 사용자 경험을 만들 수 있다는 점을 배울 수 있었습니다.

0개의 댓글