使用设备管理策略增强安全性

设备管理员弃用。当设备管理员调用一些管理员策略时,这些策略已被标记为已弃用。要了解详细信息和迁移选项,请参阅 设备管理员弃用

从 Android 2.2(API 级别 8)开始,Android 平台通过设备管理 API 提供系统级设备管理功能。

在本课程中,您将学习如何创建安全感知型应用,该应用通过强制执行设备管理策略来管理对自身内容的访问。具体来说,可以配置此应用,使其在向用户显示受限内容之前确保已设置了强度足够的屏幕锁定密码。

定义和声明策略

首先,您需要在功能级别定义要支持的策略类型。策略可以涵盖屏幕锁定密码强度、过期超时、加密等。

您必须在 res/xml/device_admin.xml 文件中声明要强制执行的所选策略集。Android 清单还应引用已声明的策略集。

每个声明的策略都对应于 DevicePolicyManager 中的某些数量的关联设备策略方法(定义最短密码长度和最少大写字母数量是两个示例)。如果应用尝试调用其对应策略未在 XML 中声明的方法,则会导致在运行时发生 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);
}

此时,应用程序能够执行策略。虽然应用程序无法访问实际的屏幕锁定密码,但它可以通过设备策略管理器 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));
}