모노산달로스의 행보

[Flutter] .env를 통해 API Key 안전하게 사용(AndroidManifest.xml에서의 사용 포함 / 환경변수 설정) 본문

App/Flutter

[Flutter] .env를 통해 API Key 안전하게 사용(AndroidManifest.xml에서의 사용 포함 / 환경변수 설정)

모노산달로스 2024. 7. 22. 21:42

Flutter - API key 값 숨기기

Flutter is an open-source UI software development kit created by Google

 

개발을 진행하면서 소셜 로그인 혹은 지도와 같은 외부 API를 사용하고는 합니다. 이때 API Key값을 발급받아 사용하는데, Github와 같은 공간에 노출되지 않도록 보호해야 합니다. dotenv package를 사용해 Key값을 안전하게 보호하면서 사용하는 방법에 대해서 알아보겠습니다. 특히나 고생했던 AndroidManifest.xml 파일에서.env에 저장된 키 값을 가져오는 방법에 대한 설명이 담겨있으니 참고하시길 바랍니다.

API Key값을 보호해야 하는 이유

Security of API keys

 

API key는 보안에 매우 취약합니다. Client(API key를 발급받는 사람)이 주의하지 않으면 다른 누군가가 API key를 훔쳐 악용할 가능성이 있습니다. 일반적으로 협업 시 깃허브에 코드를 올리게 됩니다. 이때, Public Repository에 API key가 그대로 올라가게 되면 이러한 문제가 발생합니다.

 

해당 포스트에서는 이러한 API key를 안전하게 보호하고 사용하는 방법을 알아보겠습니다.

 


dotenv package import 하기

 

flutter_dotenv | Flutter package

Easily configure any flutter application with global variables using a `.env` file.

pub.dev

flutter pub add flutter_dotenv

 

 

terminal에 명령어를 입력하여 dotenv를 사용할 준비를 합니다.

 

.env 파일 생성하고 사용할 준비하기

 

원하는 경로에 생성합니다. 글쓴이는 assets/config 폴더 내부에 .env 파일을 생성했습니다.

pubspec.yaml file

pubspec.yaml 파일에도 위와 같이 .env를 사용할 수 있도록 설정합니다.

 

.gitignore file

마지막으로 .gitignore 파일에도 .env를 추가하여 깃허브에 올라가지 않도록 합니다. 이제 .env파일을 사용할 준비가 완료되었습니다.

 

.env file

원하는 API key를 .env파일 내부에 작성합니다.

 

 


.env에 저장된 키 값 가져와서 사용하기

앞서 .env에 변수를 만들어 키 값을 저장했습니다. 이제 해당 키 값을 가져와서 사용해 보겠습니다. 먼저 main.dart와 같은 일반적인 lib폴더 내부 파일에서 사용하는 방법을 설명하겠습니다. 우선 키 값에 접근하기 위해서 .env파일을 불러옵니다. dotenv.load() 메서드를 통해서 불러올 수 있습니다.

import 'package:flutter_dotenv/flutter_dotenv.dart';

void main() async {
  await dotenv.load(fileName: 'assets/config/.env');
  runApp(const MyApp());
}

 

비동기적으로 실행되어야 하기 때문에, await를 사용합니다. fileName에 자신의 .env 파일의 경로를 입력합니다.

 

void main() async {
  await dotenv.load(fileName: 'assets/config/.env');
  String? kakaoNativeAppKey = dotenv.env['KAKAO_APP_KEY'];
  runApp(const MyApp());
}

 

다음으로 dotenv.env['VAR_NAME']; 를 통해서 원하는 키 값을 가져옵니다. 우리는 KAKAO_APP_KEY라는 이름으로 저장을 하였으니 위와 같이 입력합니다. 마지막으로 kakaoNativeAppKey 변수를 만들어 불러온 키 값을 대입합니다.

 

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await dotenv.load(fileName: 'assets/config/.env');
  String? kakaoNativeAppKey = dotenv.env['KAKAO_NATIVE_APP_KEY'];
  KakaoSdk.init(
    nativeAppKey: kakaoNativeAppKey,
  );
  runApp(const MyApp());
}

 

이제 원하는 곳에 해당 변수 통해서 키 값을 사용합니다. 이렇게 사용하면 해당 코드를 통해서 API key 값의 내용을 확인할 수가 없습니다. 즉, 깃허브에 코드가 공유되어도 키 값은 보호되는 것입니다.

 

추가적으로 WidgetsFlutterBinding.ensureInitialized(); 메서드를 추가합니다. 이는 Flutter 프레임워크가 잘 초기화되어 있는지 확인하는 역할을 합니다. 주로 비동기 작업을 수행하는 경우 선언해 줍니다.

 

코드 전문

main.dart

import 'package:flutter_dotenv/flutter_dotenv.dart';

void main() async {
  await dotenv.load(fileName: 'assets/config/.env');
  WidgetsFlutterBinding.ensureInitialized();
  String? kakaoNativeAppKey = dotenv.env['KAKAO_NATIVE_APP_KEY'];
  KakaoSdk.init(
    nativeAppKey: kakaoNativeAppKey,
  );
  runApp(const MyApp());
}

AndroidManifest.xml으로 .env에 저장된 key값 가져오기

Android 환경 설정을 위해 AndroidManifest.xml 파일을 수정합니다

 

하지만 API key 값을 AndroidManifest.xml 파일에서 사용해야 한다면 어떻게 해야 할까요? 해당 부분에 대한 설명을 찾기가 너무 힘들어 직접 해결하고 공유합니다.

 

   <!-- 카카오 로그인 커스텀 URL 스킴 설정 -->
    <activity 
        android:name="com.kakao.sdk.flutter.AuthCodeCustomTabsActivity"
        android:exported="true">
        <intent-filter android:label="flutter_web_auth">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />

            <!-- "kakao${YOUR_NATIVE_APP_KEY}://oauth" 형식의 앱 실행 스킴 설정 -->
            <!-- 카카오 로그인 Redirect URI -->
            <data android:scheme="kakao${YOUR_NATIVE_APP_KEY}" android:host="oauth"/>
        </intent-filter>
    </activity>

 

카카오 로그인을 사용하기 위해서는 위와 같은 코드를 android/app/src/main/AndroidManifest.xml 파일에 추가해야 합니다. 

 

<data android:scheme="kakao${YOUR_NATIVE_APP_KEY}" android:host="oauth"/>

 

추가적으로 위 부분을 자신의 API Key로 바꾸어줍니다. 즉, "kakao${YOUR_NATIVE_APP_KEY}"를 "kakaoabcdefg1234567"로 바뀌어야 합니다. 하지만 이렇게 직접적으로 바꿔주게 되면 마찬가지로 API key가 노출되는 상황이 발생합니다. 따라서 .env파일에 저장된 키 값을 불러오는 방법을 사용하겠습니다.

 


 

AndroidManifest.xml 파일의 ${YOUR_NATIVE_APP_KEY} 부분은 Placeholder라고 합니다. 이는 문자열 내에서 동적으로 값을 받아오는 역할을 합니다. 다시 말해, Placeholder 부분은 앱이 실제로 빌드될 때 실제 값으로 바뀌게 됩니다.

// .env 파일 경로 설정
def dotenv = new Properties()

 

placeholder 값을 지정하기 위해서는 android/app/build.gradle 파일을 수정해야 합니다. 위와 같은 코드를 build.gradle 파일 내부 아무 위치에 추가합니다. Properties() 객체는 설정 값을 키-값 쌍으로 지정하는 역할을 합니다. 주로 설정 파일을 읽고 사용하는 경우에 사용합니다.

 

// .env 파일 경로 설정
def dotenv = new Properties()
def envFile = file("${rootProject.projectDir}/../assets/config/.env")

 

다음으로 .env 파일의 경로를 envFile 변수에 대입합니다. 여기서 ${rootProject.projectDir}는 Gradle 파일의 루트 디렉터리를 의미합니다. 즉, android 폴더를 의미하며 해당 위치에서 시작하여 .env파일 경로를 찾아갑니다.

 

// .env 파일 경로 설정
def dotenv = new Properties()
def envFile = file("${rootProject.projectDir}/../assets/config/.env")
if (envFile.exists()) {
    envFile.withInputStream { stream -> dotenv.load(stream) }
} else {
    throw new FileNotFoundException("Could not find .env file at: ${envFile.path}")
}

 

마지막으로 envFile을 잘 불러왔는지 검사를 진행합니다. 만약 잘 불러왔다면 envFile.withInputStream을 통하여 파일을 입력 스트림으로 엽니다. 그리고 파일의 내용을 dotenv에 load 합니다. 이제 dotenv 객체에 .env 파일의 내용이 키-값 쌍으로 존재하게 됩니다.

 


// manifestPlaceholders 추가 및 null 체크
def kakaoKey = dotenv['KAKAO_APP_KEY']
if (kakaoKey == null) {
    throw new GradleException("KAKAO_NATIVE_APP_KEY not found in .env file")
}
manifestPlaceholders = [YOUR_NATIVE_APP_KEY: kakaoKey]

 

이제 dotenv객체에 저장된 키 값을 Placeholder 값으로 사용하겠습니다. 위와 같은 코드를 defaultConfig{ } 내부에 작성합니다. dotenv['KAKAO_APP_KEY'] 을 통해서 .env에 저장했던 키 값을 가져옵니다. 그리고 잘 불러왔는지 null 체크를 진행합니다.

 

manifestPlaceholders는 Android 빌드 시스템에서 AndroidManifest.xml 파일의 Placeholder를 실제 값으로 치환하기 위해서 사용합니다. 즉, [YOUR_NATIVE_APP_KEY: kakaokey]는 ${YOUR_NATIVE_APP_KEY} Placeholder를 .env에서 가져온 kakaokey 값으로 치환하는 역할을 수행합니다.

 

<data android:scheme="kakaoabcdefg1234567" android:host="oauth"/>

 

이제 빌드를 하게 되면 AndroidManifest.xml 파일은 위와 같은 모습을 하게 됩니다. 물론 위와 같이 코드가 바뀌는 것은 아닙니다. 즉, 키 값을 확인할 수는 없습니다. 우리는 이제 API key 값을 잘 보호하면서 AndroidManifest.xml 파일에서 사용할 수 있게 되었습니다.

 

코드 전문

android/app/build.gradle

...

// .env 파일 경로 설정
def dotenv = new Properties()
def envFile = file("${rootProject.projectDir}/../assets/config/.env")
if (envFile.exists()) {
    envFile.withInputStream { stream -> dotenv.load(stream) }
} else {
    throw new FileNotFoundException("Could not find .env file at: ${envFile.path}")
}

...

defaultConfig {
		...

        // manifestPlaceholders 추가 및 null 체크
        def kakaoKey = dotenv['KAKAO_NATIVE_APP_KEY']
        if (kakaoKey == null) {
            throw new GradleException("KAKAO_NATIVE_APP_KEY not found in .env file")
        }
        manifestPlaceholders = [YOUR_NATIVE_APP_KEY: kakaoKey]
    }
   
...

 

android/app/src/main/AndroidManifest.xml

     <!-- 카카오 로그인 커스텀 URL 스킴 설정 -->
    <activity 
        android:name="com.kakao.sdk.flutter.AuthCodeCustomTabsActivity"
        android:exported="true">
        <intent-filter android:label="flutter_web_auth">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />

            <!-- "kakao${YOUR_NATIVE_APP_KEY}://oauth" 형식의 앱 실행 스킴 설정 -->
            <!-- 카카오 로그인 Redirect URI -->
            <data android:scheme="kakao${YOUR_NATIVE_APP_KEY}" android:host="oauth"/>
        </intent-filter>
    </activity>