Best practice per la sicurezza delle app

Rendendo la tua app più sicura, contribuisci a preservare la fiducia degli utenti e l'integrità del dispositivo.

Questa pagina presenta diverse best practice che hanno un impatto significativo e positivo sulla sicurezza della tua app.

Forza comunicazione sicura

Quando proteggi i dati scambiati tra la tua app e altre app o tra la tua app e un sito web, migliori la stabilità dell'app e proteggi i dati che invii e ricevi.

Salvaguardare la comunicazione tra le app

Per comunicare tra le app in modo più sicuro, utilizza intent impliciti con un selettore di app, autorizzazioni basate sulla firma e fornitori di contenuti non esportati.

Mostra un selettore di app

Se un intent implicito può avviare almeno due possibili app sul dispositivo di un utente, mostra esplicitamente un selettore di app. Questa strategia di interazione consente agli utenti di trasferire informazioni sensibili a un'app che ritengono attendibile.

Kotlin

val intent = Intent(Intent.ACTION_SEND)
val possibleActivitiesList: List<ResolveInfo> =
        packageManager.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)
}

Java

Intent intent = new Intent(Intent.ACTION_SEND);
List<ResolveInfo> possibleActivitiesList = getPackageManager()
        .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);
}

Informazioni correlate:

Applica autorizzazioni basate sulla firma

Quando condividi dati tra due app controllate da te o di tua proprietà, utilizza le autorizzazioni basate sulla firma. Queste autorizzazioni non richiedono la conferma dell'utente e verifica invece che le app che accedono ai dati siano firmate utilizzando la stessa chiave di firma. Pertanto, queste autorizzazioni offrono un'esperienza utente più semplice e sicura.

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

Informazioni correlate:

Non consentire l'accesso ai fornitori di contenuti dell'app

A meno che tu non intenda inviare dati dalla tua app a un'app diversa che non è di tua proprietà, impedisci esplicitamente alle app di altri sviluppatori di accedere agli oggetti ContentProvider dell'app. Questa impostazione è particolarmente importante se la tua app può essere installata su dispositivi con Android 4.1.1 (livello API 16) o versioni precedenti, poiché l'attributo android:exported dell'elemento <provider> è true per impostazione predefinita su queste versioni di Android.

<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>

Chiedi le credenziali prima di mostrare informazioni sensibili

Quando richiedi credenziali agli utenti per consentire loro di accedere a informazioni sensibili o a contenuti premium nella tua app, richiedi un PIN/una password/una sequenza o una credenziale biometrica, ad esempio il riconoscimento del volto o dell'impronta.

Per scoprire di più su come richiedere le credenziali biometriche, consulta la guida all'autenticazione biometrica.

Applicare misure di sicurezza della rete

Le sezioni seguenti descrivono come migliorare la sicurezza di rete della tua app.

Utilizza il traffico TLS

Se la tua app comunica con un server web con un certificato emesso da un'autorità di certificazione (CA) nota e attendibile, utilizza una richiesta HTTPS come la seguente:

Kotlin

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

Java

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

Aggiungi una configurazione di sicurezza di rete

Se la tua app utilizza CA nuove o personalizzate, puoi dichiarare le impostazioni di sicurezza della rete in un file di configurazione. Questo processo consente di creare la configurazione senza modificare il codice dell'app.

Per aggiungere un file di configurazione della sicurezza di rete all'app:

  1. Dichiara la configurazione nel file manifest dell'app:
  2. <manifest ... >
        <application
            android:networkSecurityConfig="@xml/network_security_config"
            ... >
            <!-- Place child elements of <application> element here. -->
        </application>
    </manifest>
    
  3. Aggiungi un file di risorse XML, situato all'indirizzo res/xml/network_security_config.xml.

    Specifica che tutto il traffico verso determinati domini deve utilizzare HTTPS disabilitando il testo in chiaro:

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

    Durante il processo di sviluppo, puoi utilizzare l'elemento <debug-overrides> per consentire esplicitamente i certificati installati dall'utente. Questo elemento sostituisce le opzioni fondamentali per la sicurezza dell'app durante il debug e i test senza influire sulla configurazione della release dell'app. Lo snippet seguente mostra come definire questo elemento nel file XML di configurazione della sicurezza di rete dell'app:

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

Informazioni correlate: Configurazione della sicurezza di rete

Crea il tuo gestore trust

Il controllo TLS non dovrebbe accettare tutti i certificati. Potrebbe essere necessario configurare un gestore di attendibilità e gestire tutti gli avvisi TLS che si verificano se una delle seguenti condizioni si applica al tuo caso d'uso:

  • Stai comunicando con un server web che ha un certificato firmato da una CA nuova o personalizzata.
  • La CA non è considerata attendibile dal dispositivo in uso.
  • Non puoi utilizzare una configurazione di sicurezza di rete.

Per ulteriori informazioni su come completare questi passaggi, vedi la discussione sulla gestione di un'autorità di certificazione sconosciuta.

Informazioni correlate:

Utilizza con attenzione gli oggetti WebView

WebView nella tua app non deve consentire agli utenti di raggiungere siti al di fuori del tuo controllo. Quando possibile, utilizza una lista consentita per limitare i contenuti caricati dagli oggetti WebView dell'app.

Inoltre, non attivare mai il supporto dell'interfaccia JavaScript, a meno che tu non abbia completamente il controllo e l'attendibilità dei contenuti negli oggetti WebView della tua app.

Utilizzare canali di messaggi HTML

Se la tua app deve utilizzare il supporto dell'interfaccia JavaScript su dispositivi con Android 6.0 (livello API 23) e versioni successive, usa canali di messaggi HTML anziché comunicare tra un sito web e la tua app, come mostrato nel seguente snippet di codice:

Kotlin

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

// channel[0] and channel[1] represent the two ports.
// They are already entangled with 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"))

Java

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

// channel[0] and channel[1] represent the two ports.
// They are already entangled with 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"));

Informazioni correlate:

Fornisci le autorizzazioni corrette

Richiedi solo il numero minimo di autorizzazioni necessarie per il corretto funzionamento dell'app. Quando possibile, rinuncia alle autorizzazioni quando la tua app non le richiede più.

Utilizzare gli intent per rimandare le autorizzazioni

Se possibile, non aggiungere alla tua app un'autorizzazione per completare un'azione che può essere completata in un'altra app. Utilizza invece un intent per rimandare la richiesta a un'altra app che dispone già dell'autorizzazione necessaria.

L'esempio seguente mostra come utilizzare un intent per indirizzare gli utenti a un'app Contatti anziché richiedere le autorizzazioni READ_CONTACTS e 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)
    }
}

Java

// 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);
}

Inoltre, se l'app deve eseguire I/O basati su file, ad esempio accedere allo spazio di archiviazione o scegliere un file, non ha bisogno di autorizzazioni speciali perché il sistema può completare le operazioni per conto dell'app. Ancora meglio, dopo che un utente seleziona i contenuti in un determinato URI, all'app che effettua la chiamata viene concessa l'autorizzazione alla risorsa selezionata.

Informazioni correlate:

Condividi i dati in sicurezza tra le app

Segui queste best practice per condividere i contenuti della tua app con altre app in modo più sicuro:

Il seguente snippet di codice mostra come utilizzare i flag di concessione delle autorizzazioni URI e le autorizzazioni dei fornitori di contenuti per visualizzare il file PDF di un'app in un'app separata per il visualizzatore di 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)
    }
}

Java

// 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);
}

Nota: l'esecuzione di file dalla home directory dell'app scrivibile costituisce una violazione W^X. Per questo motivo, le app non attendibili destinate ad Android 10 (livello API 29) e versioni successive non possono richiamare exec() sui file all'interno della home directory dell'app, ma solo il codice binario incorporato nel file APK di un'app. Inoltre, le app destinate ad Android 10 e versioni successive non possono modificare in memoria il codice eseguibile dei file aperti con dlopen(). Sono inclusi i file di oggetti condivisi (.so) con spostamenti del testo.

Informazioni correlate: android:grantUriPermissions

Archivia i dati in sicurezza

Anche se la tua app potrebbe richiedere l'accesso a informazioni utente sensibili, gli utenti concedono l'accesso ai loro dati solo se ritengono che tu ritenga che tu li proteggi correttamente.

Archivia i dati privati nella memoria interna

Archivia tutti i dati privati dell'utente nella memoria interna del dispositivo, sottoposta a sandbox per app. La tua app non deve richiedere l'autorizzazione per visualizzare i file e altre app non possono accedere ai file. Come misura di sicurezza aggiuntiva, quando l'utente disinstalla un'app, il dispositivo elimina tutti i file salvati dall'app nella memoria interna.

Nota : se i dati che stai archiviando sono particolarmente sensibili o privati, valuta la possibilità di utilizzare gli oggetti EncryptedFile, disponibili nella libreria di sicurezza, anziché gli oggetti File.

Il seguente snippet di codice mostra un modo per scrivere dati nello spazio di archiviazione:

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 mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

// Create a file with this name or replace an entire existing file
// that has the same name. Note that you cannot append to an existing file,
// and the filename cannot contain path separators.
val fileToWrite = "my_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(
    File(DIRECTORY, fileToWrite),
    applicationContext,
    mainKeyAlias,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

val fileContent = "MY SUPER-SECRET INFORMATION"
        .toByteArray(StandardCharsets.UTF_8)
encryptedFile.openFileOutput().apply {
    write(fileContent)
    flush()
    close()
}

Java

Context context = getApplicationContext();

// 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 mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

// Create a file with this name or replace an entire existing file
// that has the same name. Note that you cannot append to an existing file,
// and the filename cannot contain path separators.
String fileToWrite = "my_sensitive_data.txt";
EncryptedFile encryptedFile = new EncryptedFile.Builder(
        new File(DIRECTORY, fileToWrite),
        context,
        mainKeyAlias,
        EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build();

byte[] fileContent = "MY SUPER-SECRET INFORMATION"
        .getBytes(StandardCharsets.UTF_8);
OutputStream outputStream = encryptedFile.openFileOutput();
outputStream.write(fileContent);
outputStream.flush();
outputStream.close();

Il seguente snippet di codice mostra l'operazione inversa, leggendo i dati dallo spazio di archiviazione:

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 mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

val fileToRead = "my_sensitive_data.txt"
val encryptedFile = EncryptedFile.Builder(
    File(DIRECTORY, fileToRead),
    applicationContext,
    mainKeyAlias,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

val inputStream = encryptedFile.openFileInput()
val byteArrayOutputStream = ByteArrayOutputStream()
var nextByte: Int = inputStream.read()
while (nextByte != -1) {
    byteArrayOutputStream.write(nextByte)
    nextByte = inputStream.read()
}

val plaintext: ByteArray = byteArrayOutputStream.toByteArray()

Java

Context context = getApplicationContext();

// 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 mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec);

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

InputStream inputStream = encryptedFile.openFileInput();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int nextByte = inputStream.read();
while (nextByte != -1) {
    byteArrayOutputStream.write(nextByte);
    nextByte = inputStream.read();
}

byte[] plaintext = byteArrayOutputStream.toByteArray();

Informazioni correlate:

Archivia i dati in un'unità di archiviazione esterna in base al caso d'uso

Utilizza una memoria esterna per i file di grandi dimensioni e non sensibili specifici per la tua app e per i file che l'app condivide con altre app. Le API specifiche che utilizzi variano a seconda che l'app sia progettata per accedere a file specifici dell'app o ai file condivisi.

Se un file non contiene informazioni private o sensibili, ma fornisce un valore all'utente solo nella tua app, archivialo in una directory specifica dell'app su spazio di archiviazione esterno.

Se la tua app deve accedere o archiviare un file che offre valore ad altre app, utilizza una delle seguenti API, a seconda del caso d'uso:

Verifica la disponibilità del volume di archiviazione

Se la tua app interagisce con un dispositivo di archiviazione esterno rimovibile, tieni presente che l'utente potrebbe rimuovere il dispositivo di archiviazione mentre l'app tenta di accedervi. Includi la logica per verificare che il dispositivo di archiviazione sia disponibile.

Verificare la validità dei dati

Se l'app utilizza dati di unità di archiviazione esterne, assicurati che i contenuti dei dati non siano stati danneggiati o modificati. Includi la logica per gestire i file che non sono più in un formato stabile.

Il seguente snippet di codice include un esempio di strumento di verifica degli hash:

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) }
    }
}

Java

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;
}

Archivia solo dati non sensibili nei file cache

Per fornire un accesso più rapido ai dati delle app non sensibili, archiviali nella cache del dispositivo. Per cache superiori a 1 MB, utilizza getExternalCacheDir(). Per cache di massimo 1 MB, utilizza getCacheDir(). Entrambi i metodi forniscono l'oggetto File che contiene i dati memorizzati nella cache dell'app.

Il seguente snippet di codice mostra come memorizzare nella cache un file scaricato di recente dalla tua app:

Kotlin

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

Java

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

Nota: se utilizzi getExternalCacheDir() per inserire la cache dell'app all'interno dello spazio di archiviazione condiviso, l'utente potrebbe espellere il supporto contenente questo spazio di archiviazione mentre l'app è in esecuzione. Includi la logica per gestire correttamente il fallimento della cache causato da questo comportamento dell'utente.

Attenzione : per questi file non è applicata alcuna protezione. Pertanto, qualsiasi app che ha come target Android 10 (livello API 29) o versioni precedenti e che disponga dell'autorizzazione WRITE_EXTERNAL_STORAGE può accedere ai contenuti di questa cache.

Informazioni correlate: Panoramica dell'archiviazione di dati e file

Utilizza SharedPreferences in modalità privata

Quando utilizzi getSharedPreferences() per creare o accedere agli oggetti SharedPreferences dell'app, utilizza MODE_PRIVATE. In questo modo, solo la tua app può accedere alle informazioni contenute nel file delle preferenze condivise.

Se vuoi condividere dati tra app, non utilizzare oggetti SharedPreferences. Segui invece la procedura per condividere i dati in sicurezza tra le app.

La libreria di sicurezza fornisce anche la classe EncryptedSharedPreferences, che esegue il wrapping della classe SharedPreferences e cripta automaticamente chiavi e valori.

Informazioni correlate:

Mantieni aggiornati servizi e dipendenze

La maggior parte delle app utilizza librerie esterne e informazioni di sistema del dispositivo per completare attività specializzate. Mantenendo aggiornate le dipendenze dell'app, rendi questi punti di comunicazione più sicuri.

Consultare il fornitore di servizi di sicurezza di Google Play Services

Nota: questa sezione riguarda soltanto le app destinate ai dispositivi su cui sono installati Google Play Services.

Se la tua app utilizza Google Play Services, assicurati che sia aggiornata sul dispositivo su cui è installata. Eseguire il controllo in modo asincrono, al di fuori del thread dell'interfaccia utente. Se il dispositivo non è aggiornato, attiva un errore di autorizzazione.

Per determinare se Google Play Services è aggiornato sul dispositivo su cui è installata la tua app, segui i passaggi nella guida Aggiornare il provider di sicurezza per la protezione dagli exploit SSL.

Informazioni correlate:

Aggiorna tutte le dipendenze dell'app

Prima di eseguire il deployment dell'app, assicurati che tutte le librerie, gli SDK e le altre dipendenze siano aggiornati:

  • Per le dipendenze proprietarie, come l'SDK Android, utilizza gli strumenti di aggiornamento disponibili in Android Studio, come SDK Manager.
  • Per le dipendenze di terze parti, controlla i siti web delle librerie utilizzate dalla tua app e installa gli aggiornamenti e le patch di sicurezza disponibili.

Informazioni correlate: Aggiungere dipendenze delle build

Ulteriori informazioni

Per scoprire di più su come rendere la tua app più sicura, visualizza le seguenti risorse: