项目概述

权限管理与用户认证是现代应用开发中的核心功能。在 KMP 框架下,我们需要实现跨平台的权限请求机制和用户认证系统。本文详细介绍了如何在 Kotlin Multiplatform 中实现权限管理、OAuth 认证、以及鸿蒙系统特定的权限处理方式。

第一部分:权限管理核心概念

权限系统架构

权限管理涉及多个层面的实现:

// commonMain/kotlin/com/example/permissions/PermissionManager.kt
expect class PermissionManager {
    suspend fun requestPermission(permission: String): Boolean
    suspend fun requestMultiplePermissions(permissions: List<String>): Map<String, Boolean>
    suspend fun hasPermission(permission: String): Boolean
    suspend fun shouldShowPermissionRationale(permission: String): Boolean
}

// 权限类型定义
object Permissions {
    const val CAMERA = "android.permission.CAMERA"
    const val LOCATION = "android.permission.ACCESS_FINE_LOCATION"
    const val CONTACTS = "android.permission.READ_CONTACTS"
    const val CALENDAR = "android.permission.READ_CALENDAR"
    const val MICROPHONE = "android.permission.RECORD_AUDIO"
    const val STORAGE = "android.permission.READ_EXTERNAL_STORAGE"
}

认证系统架构

用户认证系统包括多种认证方式:

// commonMain/kotlin/com/example/auth/AuthManager.kt
sealed class AuthResult {
    data class Success(val token: String, val user: User) : AuthResult()
    data class Error(val message: String, val code: Int) : AuthResult()
    object Loading : AuthResult()
}

data class User(
    val id: String,
    val email: String,
    val name: String,
    val avatar: String,
    val createdAt: Long
)

expect class AuthManager {
    suspend fun login(email: String, password: String): AuthResult
    suspend fun register(email: String, password: String, name: String): AuthResult
    suspend fun logout(): Boolean
    suspend fun refreshToken(): Boolean
    suspend fun getCurrentUser(): User?
    suspend fun oauthLogin(provider: String): AuthResult
}

第二部分:平台特定实现

Android 权限实现

在 Android 平台上,权限管理通过 ActivityResult API 实现,这是 Android 11+ 推荐的做法。我们使用 registerForActivityResult 来处理权限请求的回调。核心思路是检查权限是否已授予,如果没有则弹出权限对话框。

// androidMain/kotlin/com/example/permissions/PermissionManager.kt
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.content.ContextCompat
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts

actual class PermissionManager(private val context: Context) {
    private var permissionLauncher: ActivityResultLauncher<Array<String>>? = null
    
    actual suspend fun requestPermission(permission: String): Boolean {
        return if (ContextCompat.checkSelfPermission(
            context,
            permission
        ) == PackageManager.PERMISSION_GRANTED) {
            true
        } else {
            requestPermissionAsync(arrayOf(permission))
        }
    }
    
    actual suspend fun requestMultiplePermissions(
        permissions: List<String>
    ): Map<String, Boolean> {
        val results = mutableMapOf<String, Boolean>()
        permissions.forEach { permission ->
            results[permission] = requestPermission(permission)
        }
        return results
    }
    
    actual suspend fun hasPermission(permission: String): Boolean {
        return ContextCompat.checkSelfPermission(
            context,
            permission
        ) == PackageManager.PERMISSION_GRANTED
    }
    
    actual suspend fun shouldShowPermissionRationale(permission: String): Boolean {
        return ActivityCompat.shouldShowRequestPermissionRationale(
            context as Activity,
            permission
        )
    }
    
    private suspend fun requestPermissionAsync(permissions: Array<String>): Boolean {
        return suspendCancellableCoroutine { continuation ->
            permissionLauncher = (context as Activity).registerForActivityResult(
                ActivityResultContracts.RequestMultiplePermissions()
            ) { results ->
                continuation.resume(results.values.all { it })
            }
            permissionLauncher?.launch(permissions)
        }
    }
}

// Android OAuth 实现
actual class AuthManager(private val context: Context) {
    private val googleSignInClient = GoogleSignIn.getClient(context, GoogleSignInOptions.DEFAULT_SIGN_IN)
    
    actual suspend fun oauthLogin(provider: String): AuthResult {
        return when (provider) {
            "google" -> {
                try {
                    val task = Tasks.await(googleSignInClient.silentSignIn())
                    val account = task.getResult(ApiException::class.java)
                    val idToken = account?.idToken ?: return AuthResult.Error("No token", -1)
                    AuthResult.Success(idToken, User(
                        id = account.id ?: "",
                        email = account.email ?: "",
                        name = account.displayName ?: "",
                        avatar = account.photoUrl?.toString() ?: "",
                        createdAt = System.currentTimeMillis()
                    ))
                } catch (e: Exception) {
                    AuthResult.Error(e.message ?: "Google login failed", -1)
                }
            }
            else -> AuthResult.Error("Unknown provider", -1)
        }
    }
}

上面的代码展示了 Android 权限管理的完整流程。requestPermission 方法首先检查权限状态,如果已授予则直接返回 true。否则,它调用 requestPermissionAsync 来显示权限对话框。这个方法使用 suspendCancellableCoroutine 将回调转换为协程,使得异步操作可以以同步的方式编写。

iOS 权限实现

iOS 的权限管理与 Android 不同,它使用特定的框架来处理不同类型的权限。例如,摄像头权限通过 AVCaptureDevice 处理,位置权限通过 CLLocationManager 处理。每种权限都有其特定的 API。

// iosMain/kotlin/com/example/permissions/PermissionManager.kt
import platform.AVFoundation.*
import platform.CoreLocation.*
import platform.Contacts.*
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume

actual class PermissionManager {
    actual suspend fun requestPermission(permission: String): Boolean {
        return suspendCancellableCoroutine { continuation ->
            when (permission) {
                "camera" -> {
                    AVCaptureDevice.requestAccessForMediaType(AVMediaTypeVideo) { granted ->
                        continuation.resume(granted)
                    }
                }
                "location" -> {
                    val locationManager = CLLocationManager()
                    locationManager.requestWhenInUseAuthorization()
                    continuation.resume(true)
                }
                else -> continuation.resume(false)
            }
        }
    }
    
    actual suspend fun hasPermission(permission: String): Boolean {
        return when (permission) {
            "camera" -> AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo) == AVAuthorizationStatusAuthorized
            "location" -> CLLocationManager.authorizationStatus() == kCLAuthorizationStatusAuthorizedWhenInUse
            else -> false
        }
    }
}

iOS 的权限请求使用回调模式,通过 suspendCancellableCoroutine 将其转换为协程。这样可以在 Kotlin 中以同步的方式处理异步的权限请求。

第三部分:编译后的代码形式

JavaScript 编译版本

在 Web 平台上,权限管理通过 Permissions API 实现。这是一个标准的浏览器 API,允许网页请求特定的权限,如摄像头、麦克风等。与原生平台不同,Web 权限的粒度较粗,但基本概念是相同的。

// 权限管理器 (JavaScript/Web)
class PermissionManager {
    async requestPermission(permission) {
        try {
            const result = await navigator.permissions.query({
                name: this.mapPermission(permission)
            });
            return result.state === 'granted';
        } catch (error) {
            console.error('Permission request failed:', error);
            return false;
        }
    }
    
    mapPermission(permission) {
        const map = {
            'camera': 'camera',
            'microphone': 'microphone',
            'location': 'geolocation',
            'storage': 'storage'
        };
        return map[permission] || permission;
    }
    
    async requestMultiplePermissions(permissions) {
        const results = {};
        for (const permission of permissions) {
            results[permission] = await this.requestPermission(permission);
        }
        return results;
    }
}

// OAuth 认证
class AuthManager {
    async oauthLogin(provider) {
        if (provider === 'google') {
            return new Promise((resolve, reject) => {
                gapi.auth2.getAuthInstance().signIn().then(
                    (user) => {
                        const profile = user.getBasicProfile();
                        resolve({
                            token: user.getAuthResponse().id_token,
                            user: {
                                id: profile.getId(),
                                email: profile.getEmail(),
                                name: profile.getName(),
                                avatar: profile.getImageUrl(),
                                createdAt: Date.now()
                            }
                        });
                    },
                    (error) => reject(error)
                );
            });
        }
    }
}

Web 版本的权限管理相对简单。mapPermission 方法将通用权限名称映射到浏览器特定的权限名称。这种抽象使得跨平台代码可以使用统一的权限名称。

第四部分:鸿蒙系统实际应用

鸿蒙权限管理

鸿蒙系统的权限管理与 Android 类似,但使用了鸿蒙特定的 API。关键区别在于权限命名空间从 android.permission 变为 ohos.permission。我们通过 mapAndroidToOhosPermission 方法将 Android 权限映射到鸿蒙权限,这样可以在共享代码中使用统一的权限名称。

// ohos/kotlin/com/example/permissions/PermissionManager.kt
import ohos.permission.PermissionManager as OhosPermissionManager
import ohos.app.Context
import ohos.bundle.IBundleManager

actual class PermissionManager(private val context: Context) {
    private val permissionManager = OhosPermissionManager()
    
    actual suspend fun requestPermission(permission: String): Boolean {
        val ohosPermission = mapAndroidToOhosPermission(permission)
        return if (permissionManager.canRequestPermission(context, ohosPermission)) {
            permissionManager.requestPermissionsFromUser(context, arrayOf(ohosPermission), 0)
            true
        } else {
            permissionManager.isPermissionGranted(context, ohosPermission)
        }
    }
    
    actual suspend fun hasPermission(permission: String): Boolean {
        val ohosPermission = mapAndroidToOhosPermission(permission)
        return permissionManager.isPermissionGranted(context, ohosPermission)
    }
    
    private fun mapAndroidToOhosPermission(permission: String): String {
        return when (permission) {
            "android.permission.CAMERA" -> "ohos.permission.CAMERA"
            "android.permission.ACCESS_FINE_LOCATION" -> "ohos.permission.LOCATION"
            "android.permission.RECORD_AUDIO" -> "ohos.permission.MICROPHONE"
            else -> permission.replace("android", "ohos")
        }
    }
}

// 鸿蒙 OAuth 实现
actual class AuthManager(private val context: Context) {
    actual suspend fun oauthLogin(provider: String): AuthResult {
        return when (provider) {
            "huawei" -> {
                try {
                    val accountManager = AccountManager.get(context)
                    val accounts = accountManager.getAccountsByType("com.huawei.hwid")
                    if (accounts.isNotEmpty()) {
                        val account = accounts[0]
                        val token = accountManager.blockingGetAuthToken(
                            account,
                            "oauth2:https://www.huawei.com",
                            false
                        )
                        AuthResult.Success(token, User(
                            id = account.name,
                            email = account.name,
                            name = account.name,
                            avatar = "",
                            createdAt = System.currentTimeMillis()
                        ))
                    } else {
                        AuthResult.Error("No Huawei account", -1)
                    }
                } catch (e: Exception) {
                    AuthResult.Error(e.message ?: "Huawei login failed", -1)
                }
            }
            else -> AuthResult.Error("Unknown provider", -1)
        }
    }
}

鸿蒙实现展示了如何将 Android 权限映射到鸿蒙权限。这种映射方法使得我们可以在共享代码中保持一致的 API,同时在平台特定的实现中处理差异。

第五部分:高级功能

权限链式请求

在实际应用中,我们经常需要请求多个权限。权限链式请求模式允许我们以流畅的方式构建权限请求,并区分必需权限和可选权限。这样可以提供更好的用户体验——应用可以在获得必需权限后继续运行,即使可选权限被拒绝。

// commonMain/kotlin/com/example/permissions/PermissionChain.kt
class PermissionChain(private val permissionManager: PermissionManager) {
    private val requiredPermissions = mutableListOf<String>()
    private val optionalPermissions = mutableListOf<String>()
    
    fun addRequired(permission: String) = apply {
        requiredPermissions.add(permission)
    }
    
    fun addOptional(permission: String) = apply {
        optionalPermissions.add(permission)
    }
    
    suspend fun execute(): PermissionResult {
        val requiredResults = permissionManager.requestMultiplePermissions(requiredPermissions)
        val optionalResults = permissionManager.requestMultiplePermissions(optionalPermissions)
        
        val allGranted = requiredResults.values.all { it }
        val partiallyGranted = optionalResults.values.any { it }
        
        return PermissionResult(
            allGranted = allGranted,
            partiallyGranted = partiallyGranted,
            results = requiredResults + optionalResults
        )
    }
}

data class PermissionResult(
    val allGranted: Boolean,
    val partiallyGranted: Boolean,
    val results: Map<String, Boolean>
)

这个实现展示了如何使用 Builder 模式来构建权限请求。addRequiredaddOptional 方法返回 this,允许链式调用。execute 方法分别请求必需权限和可选权限,然后返回一个结果对象,其中包含所有权限的授予状态。

令牌刷新机制

在认证系统中,访问令牌通常有过期时间。令牌刷新机制允许应用在令牌过期前自动刷新,以保持用户的登录状态。这通常通过后台定时任务实现,定期检查令牌是否需要刷新。

// commonMain/kotlin/com/example/auth/TokenRefreshManager.kt
class TokenRefreshManager(
    private val authManager: AuthManager,
    private val tokenStorage: TokenStorage
) {
    private var refreshJob: Job? = null
    
    fun startAutoRefresh(refreshIntervalMs: Long = 3600000) {
        refreshJob = GlobalScope.launch {
            while (isActive) {
                delay(refreshIntervalMs)
                try {
                    if (authManager.refreshToken()) {
                        val newToken = tokenStorage.getToken()
                        notifyTokenRefreshed(newToken)
                    }
                } catch (e: Exception) {
                    notifyRefreshFailed(e)
                }
            }
        }
    }
    
    fun stopAutoRefresh() {
        refreshJob?.cancel()
    }
}

在这里插入图片描述

总结

权限管理与用户认证是应用安全的基础。通过 KMP 框架,我们可以在共享代码中定义统一的权限和认证接口,同时在各平台上实现特定的逻辑。鸿蒙系统提供了与 Android 兼容的权限模型,使得迁移相对平顺。关键是要理解各平台的权限模型差异,并在共享代码中提供灵活的抽象层。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐