透過裝置管理政策強化安全性

裝置管理員淘汰資料。裝置管理員叫用的部分管理員政策,已將其標示為已淘汰。詳情請參閱「 裝置管理員淘汰項目」一文,進一步瞭解及遷移選項。

從 Android 2.2 (API 級別 8) 開始,Android 平台透過 Device Administration API 提供系統層級的裝置管理功能。

在本課程中,您將學到如何建立安全感知應用程式,藉由強制執行裝置管理政策,管理內容存取權。具體來說,您可以透過設定,讓應用程式在向使用者顯示受限制的內容前,確保已設定充分強度的螢幕鎖定密碼。

定義並宣告政策

首先,您必須在功能層級定義支援的政策類型。政策可能涵蓋螢幕鎖定密碼強度、到期時間逾時、加密等政策。

您必須在 res/xml/device_admin.xml 檔案中宣告所選政策集,應用程式將強制執行這些政策。Android 資訊清單也應參照宣告的政策集。

每項宣告的政策都會對應到 DevicePolicyManager 中一些相關的裝置政策方法 (定義密碼長度下限和大寫字元數下限是兩個範例)。如果應用程式嘗試叫用方法中未宣告相對應的政策,這會導致執行階段發生 SecurityException。如果應用程式要管理其他種類的政策,您也可以使用其他權限 (例如 force-lock)。稍後在裝置管理員啟用程序中,使用者會在系統畫面中看到宣告的政策清單。

下列程式碼片段在 res/xml/device_admin.xml 中宣告限制密碼政策:

<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-policies>
        <limit-password />
    </uses-policies>
</device-admin>

Android 資訊清單中參照的政策宣告 XML:

<receiver android:name=".Policy$PolicyAdmin"
    android:permission="android.permission.BIND_DEVICE_ADMIN">
    <meta-data android:name="android.app.device_admin"
        android:resource="@xml/device_admin" />
    <intent-filter>
        <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
    </intent-filter>
</receiver>

建立裝置管理接收端

建立裝置管理廣播接收器,讓系統根據您宣告支援的政策,在發生相關事件時通知您。應用程式可選擇性覆寫回呼方法。

在範例應用程式中,裝置管理員會在使用者停用裝置管理員時,從共用偏好設定中清除已設定的政策。建議您考慮實作與自身用途相關的商業邏輯。例如,應用程式可能會採取一些動作來降低安全性風險,具體組合包括刪除裝置上的機密資料、停用遠端同步處理、通知管理員等等。

如要讓廣播接收器正常運作,請務必在 Android 資訊清單中註冊,如上述程式碼片段所示。

Kotlin

class PolicyAdmin : DeviceAdminReceiver() {

    override fun onDisabled(context: Context, intent: Intent) {
        // Called when the app is about to be deactivated as a device administrator.
        // Deletes previously stored password policy.
        super.onDisabled(context, intent)
        context.getSharedPreferences(APP_PREF, Activity.MODE_PRIVATE).edit().apply {
            clear()
            apply()
        }
    }
}

Java

public static class PolicyAdmin extends DeviceAdminReceiver {

    @Override
    public void onDisabled(Context context, Intent intent) {
        // Called when the app is about to be deactivated as a device administrator.
        // Deletes previously stored password policy.
        super.onDisabled(context, intent);
        SharedPreferences prefs = context.getSharedPreferences(APP_PREF, Activity.MODE_PRIVATE);
        prefs.edit().clear().commit();
    }
}

啟用裝置管理員

強制執行任何政策前,使用者必須以裝置管理員身分手動啟用應用程式。以下程式碼片段說明如何觸發設定活動,讓使用者可以在其中啟用應用程式。建議您在意圖中指定 EXTRA_ADD_EXPLANATION 額外項目,向使用者強調應用程式要求成為裝置管理員的原因。

圖 1 使用者啟用畫面,您可以提供裝置政策的說明。

Kotlin

if (!policy.isAdminActive()) {

    val activateDeviceAdminIntent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)

    activateDeviceAdminIntent.putExtra(
            DevicePolicyManager.EXTRA_DEVICE_ADMIN,
            policy.getPolicyAdmin()
    )

    // It is good practice to include the optional explanation text to
    // explain to user why the application is requesting to be a device
    // administrator. The system will display this message on the activation
    // screen.
    activateDeviceAdminIntent.putExtra(
            DevicePolicyManager.EXTRA_ADD_EXPLANATION,
            resources.getString(R.string.device_admin_activation_message)
    )

    startActivityForResult(activateDeviceAdminIntent, REQ_ACTIVATE_DEVICE_ADMIN)
}

Java

if (!policy.isAdminActive()) {

    Intent activateDeviceAdminIntent =
        new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);

    activateDeviceAdminIntent.putExtra(
        DevicePolicyManager.EXTRA_DEVICE_ADMIN,
        policy.getPolicyAdmin());

    // It is good practice to include the optional explanation text to
    // explain to user why the application is requesting to be a device
    // administrator. The system will display this message on the activation
    // screen.
    activateDeviceAdminIntent.putExtra(
        DevicePolicyManager.EXTRA_ADD_EXPLANATION,
        getResources().getString(R.string.device_admin_activation_message));

    startActivityForResult(activateDeviceAdminIntent,
        REQ_ACTIVATE_DEVICE_ADMIN);
}

如果使用者選擇「啟用」,應用程式就會成為裝置管理員,並開始設定及強制執行政策。

應用程式也必須做好準備,以便處理使用者因按下取消按鈕、返回鍵或主畫面鍵,就放棄啟用程序的幕後設定情況。因此,政策設定活動中的 onResume() 需要具備重新評估條件的邏輯,並視需求向使用者顯示「裝置管理員啟用」選項。

實作裝置政策控制器

成功啟用裝置管理員後,應用程式就會使用要求的政策設定裝置政策管理員。請注意,每個版本都會在 Android 中加入新政策。如果您使用新政策,同時支援舊版平台,那麼適合在應用程式中執行版本檢查。舉例來說,「密碼最低問題」政策僅適用於 API 級別 11 (Honeycomb) 及以上級別。以下程式碼示範如何在執行階段檢查版本。

Kotlin

private lateinit var dpm: DevicePolicyManager
private lateinit var policyAdmin: ComponentName

dpm = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
policyAdmin = ComponentName(context, PolicyAdmin::class.java)

dpm.apply {
    setPasswordQuality(policyAdmin, PASSWORD_QUALITY_VALUES[passwordQuality])
    setPasswordMinimumLength(policyAdmin, passwordLength)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        setPasswordMinimumUpperCase(policyAdmin, passwordMinUpperCase)
    }
}

Java

DevicePolicyManager dpm = (DevicePolicyManager)
        context.getSystemService(Context.DEVICE_POLICY_SERVICE);
ComponentName policyAdmin = new ComponentName(context, PolicyAdmin.class);

dpm.setPasswordQuality(policyAdmin, PASSWORD_QUALITY_VALUES[passwordQuality]);
dpm.setPasswordMinimumLength(policyAdmin, passwordLength);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    dpm.setPasswordMinimumUpperCase(policyAdmin, passwordMinUpperCase);
}

此時,應用程式即可強制執行政策。雖然應用程式無法存取實際螢幕鎖定密碼,但可透過 Device Policy Manager API,判斷現有密碼是否符合所需的政策。如果現有的螢幕鎖定密碼不足,裝置管理 API 就不會自動採取修正動作。應用程式負責在「設定」應用程式中明確啟動系統密碼變更畫面,例如:

Kotlin

if (!dpm.isActivePasswordSufficient) {
    // Triggers password change screen in Settings.
    Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD).also { intent ->
        startActivity(intent)
    }
}

Java

if (!dpm.isActivePasswordSufficient()) {
    ...
    // Triggers password change screen in Settings.
    Intent intent =
        new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
    startActivity(intent);
}

一般而言,使用者可以選取其中一種可用的鎖定機制,例如「無」、「圖案」、「PIN 碼」(數字) 或密碼 (英數字元)。在設定密碼政策後,如果安全性類型低於政策中定義的密碼類型,系統就會停用。舉例來說,如果已設定「數字」密碼品質,使用者就只能選取 PIN 碼 (數字) 或密碼 (英數字元)。

只要設定適當的螢幕鎖定密碼,妥善保護裝置後,應用程式就能存取安全內容。

Kotlin

when {
    !dpm.isAdminActive(policyAdmin) -> {
        // Activates device administrator.
        ...
    }
    !dpm.isActivePasswordSufficient -> {
        // Launches password set-up screen in Settings.
        ...
    }
    else -> {
        // Grants access to secure content.
        ...
        startActivity(Intent(context, SecureActivity::class.java))
    }
}

Java

if (!dpm.isAdminActive(..)) {
    // Activates device administrator.
    ...
} else if (!dpm.isActivePasswordSufficient()) {
    // Launches password set-up screen in Settings.
    ...
} else {
    // Grants access to secure content.
    ...
    startActivity(new Intent(context, SecureActivity.class));
}