앱 보안 권장사항

앱 보안을 강화하여 사용자의 신뢰와 기기 무결성을 유지할 수 있습니다.

이 페이지에서는 앱 보안에 유의미하고 긍정적인 영향을 미치는 몇 가지 권장사항을 설명합니다.

보안 통신 적용

여러 앱 사이 또는 앱과 웹사이트 사이에 교환하는 데이터를 보호하면 앱 안정성이 향상되고 주고받는 데이터가 보호됩니다.

암시적 인텐트 및 내보내지 않은 콘텐츠 제공업체 사용하기

앱 선택기 표시하기

암시적 인텐트가 사용자 기기에서 가능한 앱을 2개 이상 시작할 수 있는 경우에는 앱 선택기를 명시적으로 표시합니다. 이러한 상호작용 전략을 사용하면 사용자는 자신이 신뢰하는 앱에 민감한 정보를 전송할 수 있습니다.

Kotlin

    val intent = Intent(ACTION_SEND)
    val possibleActivitiesList: List<ResolveInfo> =
            queryIntentActivities(intent, PackageManager.MATCH_ALL)

    // Verify that an activity in at least two apps on the user's device
    // can handle the intent. Otherwise, start the intent only if an app
    // on the user's device can handle the intent.
    if (possibleActivitiesList.size > 1) {

        // Create intent to show chooser.
        // Title is something similar to "Share this photo with".

        val chooser = resources.getString(R.string.chooser_title).let { title ->
            Intent.createChooser(intent, title)
        }
        startActivity(chooser)
    } else if (intent.resolveActivity(packageManager) != null) {
        startActivity(intent)
    }
    

자바

    Intent intent = new Intent(Intent.ACTION_SEND);
    List<ResolveInfo> possibleActivitiesList =
            queryIntentActivities(intent, PackageManager.MATCH_ALL);

    // Verify that an activity in at least two apps on the user's device
    // can handle the intent. Otherwise, start the intent only if an app
    // on the user's device can handle the intent.
    if (possibleActivitiesList.size() > 1) {

        // Create intent to show chooser.
        // Title is something similar to "Share this photo with".

        String title = getResources().getString(R.string.chooser_title);
        Intent chooser = Intent.createChooser(intent, title);
        startActivity(chooser);
    } else if (intent.resolveActivity(getPackageManager()) != null) {
        startActivity(intent);
    }
    

관련 정보:

서명 기반 권한 적용하기

개발자가 소유하거나 제어하는 두 앱 사이에 데이터를 공유할 때 서명 기반 권한을 사용합니다. 이 권한을 사용하면 사용자 확인을 요구하지 않고 그 대신 데이터에 액세스하는 앱이 동일한 서명 키를 사용해 서명되는지 여부를 확인합니다. 따라서 사용자 환경이 더 간편하고 안전해집니다.

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.myapp">
        <permission android:name="my_custom_permission_name"
                    android:protectionLevel="signature" />
    

관련 정보:

앱 콘텐츠 제공업체 액세스 허용 안함

내 앱에서 내 소유가 아닌 다른 앱에 데이터를 보내지 않으려는 경우에는 다른 개발자의 앱이 내 앱에 포함된 ContentProvider 객체에 액세스하지 못하도록 명시적으로 지정해야 합니다. 이 설정은 Android 4.1.1(API 수준 16) 이하를 실행하는 기기에 앱을 설치할 수 있는 경우에 특히 중요합니다. <provider> 요소의 android:exported 속성은 이 버전의 Android에서 기본적으로 true이기 때문입니다.

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.myapp">
        <application ... >
            <provider
                android:name="android.support.v4.content.FileProvider"
                android:authorities="com.example.myapp.fileprovider"
                ...
                android:exported="false">
                <!-- Place child elements of <provider> here. -->
            </provider>
            ...
        </application>
    </manifest>
    

민감한 정보를 표시하기 전에 사용자 인증 정보 요청하기

사용자가 앱의 민감한 정보나 고급 콘텐츠에 액세스할 수 있도록 사용자에게 사용자 인증 정보를 요청할 때 PIN/비밀번호/패턴이나 생체 인식 사용자 인증 정보(예: 얼굴 인식, 디지털 지문 인식)를 요구합니다.

생체 인식 사용자 인증 정보를 요청하는 방법을 자세히 알아보려면 생체 인식 인증 관련 가이드를 참고하세요.

네트워크 보안 조치 적용하기

다음 섹션에서는 앱의 네트워크 보안을 향상할 수 있는 방법을 설명합니다.

SSL 트래픽 사용하기

앱이 신뢰할 수 있으며 잘 알려진 CA에서 발행한 인증서가 있는 웹 서버와 통신하는 경우 HTTPS 요청은 아래와 같이 매우 단순합니다.

Kotlin

    val url = URL("https://www.google.com")
    val urlConnection = url.openConnection() as HttpsURLConnection
    urlConnection.connect()
    urlConnection.inputStream.use {
        ...
    }
    

자바

    URL url = new URL("https://www.google.com");
    HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
    urlConnection.connect();
    InputStream in = urlConnection.getInputStream();
    

네트워크 보안 구성 추가하기

앱이 새 CA나 맞춤 CA를 사용하는 경우 구성 파일에서 네트워크의 보안 설정을 선언할 수 있습니다. 이 프로세스를 통해 앱 코드를 수정하지 않고도 구성을 만들 수 있습니다.

네트워크 보안 구성 파일을 앱에 추가하려면 다음 단계를 따르세요.

  1. 앱의 매니페스트에서 구성을 선언합니다.
  2.     <manifest ... >
            <application
                android:networkSecurityConfig="@xml/network_security_config"
                ... >
                <!-- Place child elements of <application> element here. -->
            </application>
        </manifest>
        
  3. XML 리소스 파일(위치: res/xml/network_security_config.xml)을 추가합니다.

    clear-text를 사용 중지하여 특정 도메인으로 향하는 전체 트래픽에 HTTPS를 사용하도록 지정합니다.

        <network-security-config>
            <domain-config cleartextTrafficPermitted="false">
                <domain includeSubdomains="true">secure.example.com</domain>
                ...
            </domain-config>
        </network-security-config>
        

    개발 프로세스 도중에 <debug-overrides> 요소를 사용하여 사용자가 설치한 인증서를 명시적으로 허용할 수 있습니다. 이 요소는 앱의 릴리스 구성에는 영향을 미치지 않고 디버깅 및 테스트 도중에 앱의 보안상 중요한 옵션을 재정의합니다. 다음 스니펫에서는 앱의 네트워크 보안 구성 XML 파일에서 이 요소를 정의하는 방법을 보여줍니다.

        <network-security-config>
            <debug-overrides>
                <trust-anchors>
                    <certificates src="user" />
                </trust-anchors>
            </debug-overrides>
        </network-security-config>
        

관련 정보: 네트워크 보안 구성

고유한 트러스트 관리자 만들기

SSL 검사기가 모든 인증서를 수락해서는 안 됩니다. 트러스트 관리자를 설정하고 다음 조건 중 하나가 사용 사례에 해당하는 경우 발생하는 모든 SSL 경고를 처리해야 할 수도 있습니다.

  • 새로운 CA나 맞춤 CA가 서명한 인증서가 있는 웹 서버와 통신합니다.
  • 이 CA가 사용 중인 기기에서 신뢰되지 않습니다.
  • 네트워크 보안 구성을 사용할 수 없습니다.

이 단계를 완료하는 방법을 자세히 알아보려면 알 수 없는 인증 기관을 처리하는 방법과 관련한 설명을 참고하세요.

관련 정보:

신중하게 WebView 개체 사용하기

가능한 경우 WebView 객체의 허용된 콘텐츠만 로드합니다. 즉, 앱의 WebView 객체는 제어되지 않는 사이트로 사용자가 이동하는 것을 허용해서는 안 됩니다.

또한, 앱의 WebView 객체에 있는 콘텐츠를 완벽하게 제어하고 신뢰하지 않는 경우 자바스크립트 인터페이스 지원을 절대로 사용해서는 안 됩니다.

HTML 메시지 채널 사용하기

Android 6.0(API 수준 23) 이상을 실행하는 기기에서 자바스크립트 인터페이스 지원을 사용해야 하는 경우 다음 코드 스니펫과 같이 대신 HTML 메시지 채널을 사용하여 웹사이트와 앱 사이에 통신합니다.

Kotlin

    val myWebView: WebView = findViewById(R.id.webview)

    // messagePorts[0] and messagePorts[1] represent the two ports.
    // They are already tangled to each other and have been started.
    val channel: Array<out WebMessagePort> = myWebView.createWebMessageChannel()

    // Create handler for channel[0] to receive messages.
    channel[0].setWebMessageCallback(object : WebMessagePort.WebMessageCallback() {

        override fun onMessage(port: WebMessagePort, message: WebMessage) {
            Log.d(TAG, "On port $port, received this message: $message")
        }
    })

    // Send a message from channel[1] to channel[0].
    channel[1].postMessage(WebMessage("My secure message"))
    

자바

    WebView myWebView = (WebView) findViewById(R.id.webview);

    // messagePorts[0] and messagePorts[1] represent the two ports.
    // They are already tangled to each other and have been started.
    WebMessagePort[] channel = myWebView.createWebMessageChannel();

    // Create handler for channel[0] to receive messages.
    channel[0].setWebMessageCallback(new WebMessagePort.WebMessageCallback() {
        @Override
        public void onMessage(WebMessagePort port, WebMessage message) {
             Log.d(TAG, "On port " + port + ", received this message: " + message);
        }
    });

    // Send a message from channel[1] to channel[0].
    channel[1].postMessage(new WebMessage("My secure message"));
    

관련 정보:

적합한 권한 제공하기

제대로 기능하는 데 필요한 최소 권한 수만 요청해야 합니다. 가능한 경우 앱은 더 이상 필요 없을 때 이 권한 중 일부를 포기해야 합니다.

권한을 지연하는 인텐트 사용하기

가능한 경우 다른 앱에서 완료될 수 있는 작업을 완료할 권한을 앱에 추가하지 마세요. 대신, 필수 권한이 기존에 존재하는 다른 앱으로 요청을 지연하는 인텐트를 사용합니다.

다음 예에서는 READ_CONTACTS 권한과 WRITE_CONTACTS 권한을 요청하는 대신 인텐트를 사용하여 사용자를 연락처 앱으로 안내하는 방법을 보여줍니다.

Kotlin

    // Delegates the responsibility of creating the contact to a contacts app,
    // which has already been granted the appropriate WRITE_CONTACTS permission.
    Intent(Intent.ACTION_INSERT).apply {
        type = ContactsContract.Contacts.CONTENT_TYPE
    }.also { intent ->
        // Make sure that the user has a contacts app installed on their device.
        intent.resolveActivity(packageManager)?.run {
            startActivity(intent)
        }
    }
    

자바

    // Delegates the responsibility of creating the contact to a contacts app,
    // which has already been granted the appropriate WRITE_CONTACTS permission.
    Intent insertContactIntent = new Intent(Intent.ACTION_INSERT);
    insertContactIntent.setType(ContactsContract.Contacts.CONTENT_TYPE);

    // Make sure that the user has a contacts app installed on their device.
    if (insertContactIntent.resolveActivity(getPackageManager()) != null) {
        startActivity(insertContactIntent);
    }
    

또한, 앱이 파일 기반 I/O(예: 스토리지 액세스, 파일 선택)를 수행해야 하는 경우 시스템이 앱 대신에 작업을 완료할 수 있으므로 특별한 권한이 필요하지 않습니다. 더 좋은 것은, 사용자가 특정 URI에서 콘텐츠를 선택하고 나면 선택한 리소스 관련 권한이 통화 앱에 부여됩니다.

관련 정보:

여러 앱에서 안전하게 데이터 공유하기

보다 안전한 방법으로 앱의 콘텐츠를 다른 앱과 공유하기 위해 다음 권장사항을 따르세요.

  • 필요에 따라 읽기 전용 권한이나 쓰기 전용 권한을 적용합니다.
  • FLAG_GRANT_READ_URI_PERMISSION 플래그와 FLAG_GRANT_WRITE_URI_PERMISSION 플래그를 사용하여 클라이언트에 데이터 1회 액세스 권한을 제공합니다.
  • 데이터를 공유할 때 'file://' URI 대신 'content://' URI를 사용합니다. 이 작업은 FileProvider의 인스턴스가 자동으로 처리합니다.

다음 코드 스니펫에서는 URI 권한 부여 플래그와 콘텐츠 제공업체 권한을 사용하여 앱의 PDF 파일을 별도의 PDF 뷰어 앱에 표시하는 방법을 보여줍니다.

Kotlin

    // Create an Intent to launch a PDF viewer for a file owned by this app.
    Intent(Intent.ACTION_VIEW).apply {
        data = Uri.parse("content://com.example/personal-info.pdf")

        // This flag gives the started app read access to the file.
        addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    }.also { intent ->
        // Make sure that the user has a PDF viewer app installed on their device.
        intent.resolveActivity(packageManager)?.run {
            startActivity(intent)
        }
    }
    

자바

    // Create an Intent to launch a PDF viewer for a file owned by this app.
    Intent viewPdfIntent = new Intent(Intent.ACTION_VIEW);
    viewPdfIntent.setData(Uri.parse("content://com.example/personal-info.pdf"));

    // This flag gives the started app read access to the file.
    viewPdfIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

    // Make sure that the user has a PDF viewer app installed on their device.
    if (viewPdfIntent.resolveActivity(getPackageManager()) != null) {
        startActivity(viewPdfIntent);
    }
    

참고: Android 10(API 수준 29) 이상을 타겟팅하는 신뢰할 수 없는 앱은 앱의 홈 디렉터리 내의 파일에서 exec()를 호출할 수 없습니다. 쓰기 가능한 앱의 홈 디렉터리에서 이렇게 파일을 실행하는 것은 W^X 위반입니다. 앱은 앱의 APK 파일에 삽입된 바이너리 코드만 로드해야 합니다. 또한, Android 10 이상을 타겟팅하는 앱은 dlopen()을 사용하여 연 파일에서 실행 코드를 메모리 내에서 수정할 수 없습니다. 여기에는 텍스트가 재배치된 모든 공유 객체(.so) 파일이 포함됩니다.

관련 정보: android:grantUriPermissions

안전하게 데이터 저장하기

앱이 민감한 사용자 정보에 액세스할 권한을 요구하더라도 사용자는 앱의 보안이 적절하다고 신뢰하는 경우에만 자신의 데이터에 액세스할 수 있도록 앱에 권한을 부여합니다.

내부 저장소 내에 비공개 데이터 저장하기

비공개 사용자 데이터는 앱별로 샌드박스가 배정된 기기 내부 저장소 내에 모두 저장합니다. 그러면 앱이 이러한 파일을 보기 위해 권한을 요청할 필요가 없으며, 다른 앱은 이 파일에 액세스할 수 없습니다. 추가 보안 조치로서, 사용자가 앱을 제거하면 기기에서 앱이 내부 저장소 내에 저장한 모든 파일이 삭제됩니다.

참고: 저장하는 데이터가 특히 민감하거나 비공개인 경우 File 객체 대신 EncryptedFile 객체(보안 라이브러리에서 사용 가능)를 사용하는 것을 고려해 보세요.

다음 코드 스니펫은 저장소에 데이터를 쓰는 방법을 보여줍니다.

Kotlin

    // Although you can define your own key generation parameter specification, it's
    // recommended that you use the value specified here.
    val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
    val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

    // Creates a file with this name, or replaces an existing file
    // that has the same name. Note that the file name cannot contain
    // path separators.
    val fileToWrite = "my_sensitive_data.txt"
    val encryptedFile = EncryptedFile.Builder(
        File(directory, fileToWrite),
        context,
        masterKeyAlias,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
    ).build()

    encryptedFile.openFileOutput().bufferedWriter().use {
        it.write("MY SUPER-SECRET INFORMATION")
    }
    

자바

    // Although you can define your own key generation parameter specification, it's
    // recommended that you use the value specified here.
    KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
    String masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

    // Creates a file with this name, or replaces an existing file
    // that has the same name. Note that the file name cannot contain
    // path separators.
    String fileToWrite = "my_sensitive_data.txt";
    try {
        EncryptedFile encryptedFile = new EncryptedFile.Builder(
                new File(directory, fileToWrite),
                context,
                masterKeyAlias,
                EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
        ).build();

        // Write to a file.
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
                encryptedFile.openFileOutput()));
        writer.write("MY SUPER-SECRET INFORMATION");
    } catch (GeneralSecurityException gse) {
        // Error occurred getting or creating keyset.
    } catch (IOException ex) {
        // Error occurred opening file for writing.
    }
    

다음 코드 스니펫은 저장소에서 데이터를 읽는 역작업을 보여줍니다.

Kotlin

    // Although you can define your own key generation parameter specification, it's
    // recommended that you use the value specified here.
    val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
    val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

    val fileToRead = "my_sensitive_data.txt"
    val encryptedFile = EncryptedFile.Builder(
        File(directory, fileToRead),
        context,
        masterKeyAlias,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
    ).build()

    val contents = encryptedFile.bufferedReader().useLines { lines ->
        lines.fold("") { working, line ->
            "$working\n$line"
        }
    }
    

자바

    // Although you can define your own key generation parameter specification, it's
    // recommended that you use the value specified here.
    KeyGenParameterSpec keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC;
    String masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

    String fileToRead = "my_sensitive_data.txt";
    EncryptedFile encryptedFile = new EncryptedFile.Builder(
            new File(directory, fileToRead),
            context,
            masterKeyAlias,
            EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
    ).build();

    StringBuffer stringBuffer = new StringBuffer();
    try (BufferedReader reader =
                 new BufferedReader(new FileReader(encryptedFile))) {

        String line = reader.readLine();
        while (line != null) {
            stringBuffer.append(line).append('\n');
            line = reader.readLine();
        }
    } catch (IOException e) {
        // Error occurred opening raw file for reading.
    } finally {
        String contents = stringBuffer.toString();
    }
    

관련 정보:

사용 사례별로 외부 저장소에 데이터 저장

앱이 다른 앱과 공유하는 파일뿐만 아니라 앱에 특정한 민감하지 않은 대형 파일을 저장하는 데는 외부 저장소를 사용합니다. 사용하는 특정 API는 앱이 앱별 파일에 액세스하도록 설계되었는지 또는 공유 파일에 액세스하도록 설계되었는지에 따라 다릅니다.

저장소 볼륨의 사용 가능 여부 확인

앱이 이동식 외부 저장소 기기와 상호작용하는 경우에는 앱이 저장소 기기에 액세스하는 동안 사용자가 저장소 기기 연결을 끊을 수도 있습니다. 저장소 기기를 사용할 수 있는지 확인하는 로직을 포함하세요.

앱별 파일에 액세스

파일이 비공개 정보나 민감한 정보를 포함하지 않지만 사용자가 앱을 이용 중일 때만 사용자에게 값을 제공하는 경우에는 파일을 외부 저장소의 앱별 디렉터리에 저장합니다.

공유 파일에 액세스

다른 앱에 값을 제공하는 파일을 액세스하거나 저장해야 하는 앱의 경우 사용 사례에 따라 다음 API 중 하나를 사용합니다.

데이터 유효성 확인하기

앱이 외부 저장소의 데이터를 사용하는 경우 데이터의 콘텐츠가 계산되거나 수정되지 않았는지 확인합니다. 앱은 더 이상 안정적인 형식이 아닌 파일을 처리하는 로직도 포함해야 합니다.

해시 인증 도구의 예는 다음 코드 스니펫에 나와 있습니다.

Kotlin

    val hash = calculateHash(stream)
    // Store "expectedHash" in a secure location.
    if (hash == expectedHash) {
        // Work with the content.
    }

    // Calculating the hash code can take quite a bit of time, so it shouldn't
    // be done on the main thread.
    suspend fun calculateHash(stream: InputStream): String {
        return withContext(Dispatchers.IO) {
            val digest = MessageDigest.getInstance("SHA-512")
            val digestStream = DigestInputStream(stream, digest)
            while (digestStream.read() != -1) {
                // The DigestInputStream does the work; nothing for us to do.
            }
            digest.digest().joinToString(":") { "%02x".format(it) }
        }
    }
    

자바

    Executor threadPoolExecutor = Executors.newFixedThreadPool(4);
    private interface HashCallback {
        void onHashCalculated(@Nullable String hash);
    }

    boolean hashRunning = calculateHash(inputStream, threadPoolExecutor, hash -> {
        if (Objects.equals(hash, expectedHash)) {
            // Work with the content.
        }
    });

    if (!hashRunning) {
        // There was an error setting up the hash function.
    }

    private boolean calculateHash(@NonNull InputStream stream,
                                  @NonNull Executor executor,
                                  @NonNull HashCallback hashCallback) {
        final MessageDigest digest;
        try {
            digest = MessageDigest.getInstance("SHA-512");
        } catch (NoSuchAlgorithmException nsa) {
            return false;
        }

        // Calculating the hash code can take quite a bit of time, so it shouldn't
        // be done on the main thread.
        executor.execute(() -> {
            String hash;
            try (DigestInputStream digestStream =
                    new DigestInputStream(stream, digest)) {
                while (digestStream.read() != -1) {
                    // The DigestInputStream does the work; nothing for us to do.
                }
                StringBuilder builder = new StringBuilder();
                for (byte aByte : digest.digest()) {
                    builder.append(String.format("%02x", aByte)).append(':');
                }
                hash = builder.substring(0, builder.length() - 1);
            } catch (IOException e) {
                hash = null;
            }

            final String calculatedHash = hash;
            runOnUiThread(() -> hashCallback.onHashCalculated(calculatedHash));
        });
        return true;
    }
    

민감하지 않은 데이터만 캐시 파일에 저장하기

민감하지 않은 앱 데이터에 더 빠르게 액세스할 수 있게 하려면 이 데이터를 기기의 캐시에 저장합니다. 크기가 1MB를 초과하는 캐시의 경우 getExternalCacheDir()을 사용하고, 그렇지 않으면 getCacheDir()을 사용합니다. 각 메서드는 앱의 캐시 데이터가 포함된 File 객체를 제공합니다.

다음 코드 스니펫에서는 앱에서 최근에 다운로드한 파일을 캐시하는 방법을 보여줍니다.

Kotlin

    val cacheFile = File(myDownloadedFileUri).let { fileToCache ->
        File(cacheDir.path, fileToCache.name)
    }
    

자바

    File cacheDir = getCacheDir();
    File fileToCache = new File(myDownloadedFileUri);
    String fileToCacheName = fileToCache.getName();
    File cacheFile = new File(cacheDir.getPath(), fileToCacheName);
    

참고: getExternalCacheDir()을 사용하여 앱 캐시를 공유 저장공간 내에 저장하는 경우 사용자가 앱 실행 중에 이 저장공간이 포함된 매체를 뺄 수도 있습니다. 이 사용자 동작으로 인해 발생하는 캐시 부적중을 원활하게 처리하는 로직을 포함해야 합니다.

주의: 이러한 파일에는 적용되는 보안이 없습니다. 그러므로 WRITE_EXTERNAL_STORAGE 권한이 있는 앱은 이 캐시의 콘텐츠에 액세스할 수 있습니다.

관련 정보: 캐시 파일 저장하기

비공개 모드로 SharedPreferences 사용하기

getSharedPreferences()를 사용하여 앱의 SharedPreferences 객체를 만들거나 액세스할 때는 MODE_PRIVATE을 사용하세요. 이렇게 하면 내 앱만 공유 환경설정 파일 내의 정보에 액세스할 수 있습니다.

데이터를 여러 앱에서 공유하려면 SharedPreferences 객체를 사용하지 마세요. 대신, 필요한 단계에 따라 여러 앱에서 안전하게 데이터를 공유해야 합니다.

관련 정보: 공유 환경설정 사용하기

서비스 및 종속 항목 최신 상태로 유지하기

대부분의 앱은 외부 라이브러리와 기기 시스템 정보를 사용하여 전문 작업을 완료합니다. 앱의 의존성을 최신 상태로 유지하여 통신 지점의 보안을 강화할 수 있습니다.

Google Play 서비스 보안 제공자 확인하기

참고: 이 섹션은 Google Play 서비스가 설치되어 있는 기기를 타겟팅하는 앱에만 적용됩니다.

앱이 Google Play 서비스를 사용하는 경우에는 앱이 설치된 기기에서 업데이트되도록 해야 합니다. 이러한 확인은 UI 스레드에서 비동기적으로 이루어져야 합니다. 기기가 최신 상태가 아닌 경우 앱에서 승인 오류를 트리거해야 합니다.

앱이 설치되어 있는 기기에서 Google Play 서비스의 최신 상태 여부를 확인하려면 가이드에서 SSL 악용을 차단하기 위해 보안 제공자 업데이트하기 단계를 따르세요.

관련 정보:

모든 앱 종속 항목 업데이트하기

앱을 배포하기 전에 모든 라이브러리, SDK, 기타 종속 항목이 최신 상태인지 확인합니다.

  • Android SDK와 같은 자사 종속 항목의 경우 Android 스튜디오에 있는 업데이트 도구(예: SDK Manager)를 사용합니다.
  • 타사 종속 항목의 경우는 앱에서 사용하는 라이브러리의 웹사이트를 확인하고 사용할 수 있는 업데이트와 보안 패치를 설치하세요.

관련 정보: 빌드 종속 항목 추가

추가 정보

앱의 보안을 강화하는 방법을 자세히 알아보려면 다음 리소스를 확인하세요.

추가 리소스

앱의 보안 강화와 관련한 추가 정보는 다음 리소스를 참조하세요.

Codelab

블로그