bjb 4 сар өмнө
parent
commit
4f0ed5d3b2

+ 1 - 1
.idea/deploymentTargetSelector.xml

@@ -4,7 +4,7 @@
     <selectionStates>
       <SelectionState runConfigName="app">
         <option name="selectionMode" value="DROPDOWN" />
-        <DropdownSelection timestamp="2026-01-12T05:11:25.765749800Z">
+        <DropdownSelection timestamp="2026-01-12T09:05:28.388700300Z">
           <Target type="DEFAULT_BOOT">
             <handle>
               <DeviceId pluginId="PhysicalDevice" identifier="serial=D1CGAPE741200820" />

+ 0 - 1
app/build.gradle.kts

@@ -68,7 +68,6 @@ dependencies {
     implementation(libs.androidx.compose.navigation)
     // 指纹组件
     implementation(libs.androidx.biometric)
-    implementation(libs.androidx.compose.biometric)
 
     // 携程相关包的导入
     implementation(libs.coroutines.core)

+ 1 - 1
app/src/main/AndroidManifest.xml

@@ -4,7 +4,7 @@
     <!--  网络请求  -->
     <uses-permission android:name="android.permission.INTERNET" />
     <!--  指纹验证  -->
-    <uses-permission android:name="android.permission.USE_BIOMETRIC"/>
+    <uses-permission android:name="android.permission.USE_BIOMETRIC" />
 
 
     <application

+ 2 - 1
app/src/main/java/com/iscs/bozzys/Entry.kt

@@ -3,8 +3,9 @@ package com.iscs.bozzys
 import android.app.Application
 import com.alibaba.sdk.android.push.noonesdk.PushInitConfig
 import com.alibaba.sdk.android.push.noonesdk.PushServiceFactory
-import com.alibaba.sdk.android.push.register.MiPushRegister
+import com.iscs.bozzys.utils.BiometricKeyStore
 import com.iscs.bozzys.utils.Storage
+import com.iscs.bozzys.utils.SystemUtil.isBiometricCanUse
 
 /**
  * App主入口

+ 21 - 0
app/src/main/java/com/iscs/bozzys/ui/pages/home/SettingsCompose.kt

@@ -1,6 +1,8 @@
 package com.iscs.bozzys.ui.pages.home
 
+import android.app.Activity
 import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
@@ -47,11 +49,17 @@ import com.iscs.bozzys.ui.pages.compose.CardContainer
 import com.iscs.bozzys.ui.pages.login.openPageLogin
 import com.iscs.bozzys.ui.pages.vm.VMHome
 import com.iscs.bozzys.ui.theme.Text
+import com.iscs.bozzys.utils.Storage
+import com.iscs.bozzys.utils.SystemUtil
+import com.iscs.bozzys.utils.SystemUtil.isBiometricCanUse
 
 @Composable
 fun SettingsCompose(pv: PaddingValues, zIndex: Float, vm: VMHome) {
     var showExitDialog by remember { mutableStateOf(false) }
     val ctx = LocalContext.current
+    val tokenIv = Storage.readToken()
+    // 指纹登录是否可用
+    val fingerSupport = ctx.isBiometricCanUse() && tokenIv.isNotEmpty()
     Column(
         modifier = Modifier
             .fillMaxSize()
@@ -64,6 +72,19 @@ fun SettingsCompose(pv: PaddingValues, zIndex: Float, vm: VMHome) {
         TopToolBar(pv, vm)
         // 用户信息
         UserInfo(vm)
+        // 开启指纹登录
+        if (fingerSupport) CardContainer(
+            modifier = Modifier
+                .padding(horizontal = 16.dp)
+                .height(46.dp)
+                .clickable {
+                    if (ctx is Activity) SystemUtil.showFingerAuth(ctx)
+                }
+        ) {
+            Row(modifier = Modifier.fillMaxSize()) {
+                Text("开启指纹登录")
+            }
+        }
         Spacer(modifier = Modifier.weight(1f))
         // 底部退出登录
         Button(

+ 6 - 4
app/src/main/java/com/iscs/bozzys/ui/pages/login/PageLogin.kt

@@ -53,6 +53,7 @@ import com.iscs.bozzys.ui.pages.vm.VMLogin
 import com.iscs.bozzys.ui.theme.Main
 import com.iscs.bozzys.ui.theme.Text
 import com.iscs.bozzys.ui.theme.TextDesc
+import com.iscs.bozzys.utils.Storage
 import com.iscs.bozzys.utils.SystemUtil.isBiometricCanUse
 
 /**
@@ -119,8 +120,9 @@ class PageLogin : PageBase() {
     fun LoginCompose(kb: SoftwareKeyboardController?, vm: VMLogin = viewModel()) {
         val ctx = LocalContext.current
         val state = vm.state
+        val tokenIv = Storage.readTokenIv()
         // 指纹登录是否可用
-        val finger = ctx.isBiometricCanUse()
+        val fingerSupport = ctx.isBiometricCanUse() && tokenIv.isNotEmpty()
         LaunchedEffect(Unit) {
             // 处理基础Toast和Loading提示
             vm.toast.initToast()
@@ -275,7 +277,7 @@ class PageLogin : PageBase() {
                 Text("工业安全系统,请妥善保管账号信息", Modifier.padding(start = 5.dp), fontSize = 12.sp, color = Color(0xFF6B7280))
             }
             // 其他登录方式
-            if (finger) Box(Modifier.padding(top = 20.dp), contentAlignment = Alignment.Center) {
+            if (fingerSupport) Box(Modifier.padding(top = 20.dp), contentAlignment = Alignment.Center) {
                 Spacer(
                     Modifier
                         .size(335.dp, 1.dp)
@@ -289,7 +291,7 @@ class PageLogin : PageBase() {
                 )
             }
             Row(Modifier.padding(top = 30.dp, bottom = 10.dp), verticalAlignment = Alignment.CenterVertically) {
-                if (finger) Icon(
+                if (fingerSupport) Icon(
                     painter = painterResource(R.drawable.fingerprint),
                     contentDescription = null,
                     Modifier
@@ -297,7 +299,7 @@ class PageLogin : PageBase() {
                         .size(50.dp)
                         .clip(RoundedCornerShape(50))
                         .clickable {
-                            // BiometricPrompt
+
                         }
                         .background(Color(0xFFF5F5F5))
                         .padding(13.dp),

+ 0 - 4
app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMHome.kt

@@ -16,8 +16,6 @@ import com.iscs.bozzys.event.RefreshEventBus
 import com.iscs.bozzys.ui.common.VMBase
 import com.iscs.bozzys.ui.theme.Text
 import com.iscs.bozzys.utils.Storage
-import com.iscs.bozzys.utils.Storage.saveRefreshToken
-import com.iscs.bozzys.utils.Storage.saveToken
 import kotlinx.coroutines.async
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -230,8 +228,6 @@ class VMHome : VMBase() {
             ApiRequest.logout().onSuccess {
                 // 清除本地数据
                 Storage.saveLogin(false)
-                "".saveToken()
-                "".saveRefreshToken()
                 done()
             }.onFailure {
                 toast.emit(it.getResponse<Any>().msg)

+ 14 - 0
app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMLogin.kt

@@ -8,6 +8,7 @@ import com.iscs.bozzys.R
 import com.iscs.bozzys.api.ApiRequest
 import com.iscs.bozzys.api.ApiRequest.getResponse
 import com.iscs.bozzys.ui.common.VMBase
+import com.iscs.bozzys.utils.BiometricKeyStore
 import com.iscs.bozzys.utils.Storage
 import com.iscs.bozzys.utils.Storage.saveRefreshToken
 import com.iscs.bozzys.utils.Storage.saveToken
@@ -84,6 +85,19 @@ class VMLogin : VMBase() {
         }
     }
 
+    /**
+     * 存储登录成功后生成的签名向量
+     *
+     * @param token
+     */
+    fun encryptAndSaveToken(token: String) {
+        BiometricKeyStore.createKeyIfNeeded()
+        val cipher = BiometricKeyStore.encryptCipher()
+        val encrypted = cipher.doFinal(token.toByteArray())
+        // 保存 encrypted + cipher.iv
+        // (encrypted + cipher.iv).saveTokenIv()
+    }
+
     /**
      * 检验是否可以提交登录
      */

+ 14 - 0
app/src/main/java/com/iscs/bozzys/utils/Storage.kt

@@ -89,4 +89,18 @@ object Storage {
         return (mmkv.decodeString("user_role", "") ?: "").split(",")
     }
 
+    /**
+     * 保存Token生成的向量
+     */
+    fun ByteArray.saveTokenIv(): Boolean {
+        return mmkv.encode("user_token_iv", this)
+    }
+
+    /**
+     * 读取Token向量
+     */
+    fun readTokenIv(): ByteArray {
+        return mmkv.decodeBytes("user_token_iv", byteArrayOf()) ?: byteArrayOf()
+    }
+
 }

+ 103 - 2
app/src/main/java/com/iscs/bozzys/utils/SystemUtil.kt

@@ -1,8 +1,21 @@
 package com.iscs.bozzys.utils
 
+import android.app.Activity
 import android.content.Context
+import android.os.Build
+import android.os.CancellationSignal
+import android.security.keystore.KeyGenParameterSpec
+import android.security.keystore.KeyProperties
 import android.util.Log
 import androidx.biometric.BiometricManager
+import androidx.biometric.BiometricPrompt
+import androidx.fragment.app.FragmentActivity
+import com.iscs.bozzys.utils.Storage.saveTokenIv
+import java.security.KeyStore
+import javax.crypto.Cipher
+import javax.crypto.KeyGenerator
+import javax.crypto.SecretKey
+import javax.crypto.spec.IvParameterSpec
 
 /**
  * 用于系统相关操作的功能
@@ -16,7 +29,6 @@ object SystemUtil {
     fun Context.isBiometricCanUse(): Boolean {
         val bm = BiometricManager.from(this)
         val canAuth = bm.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)
-        Log.d("xiaoming", "指纹功能是否可用:$canAuth")
         return when (canAuth) {
             // 可以使用指纹
             BiometricManager.BIOMETRIC_SUCCESS -> true
@@ -31,4 +43,93 @@ object SystemUtil {
         }
     }
 
-}
+    /**
+     * 显示指纹验证
+     *
+     * @param act
+     */
+    fun showFingerAuth(act: Activity) {
+        if (act is FragmentActivity) {
+            val bp = BiometricPrompt(act, act.mainExecutor, object : BiometricPrompt.AuthenticationCallback() {
+
+            })
+        } else {
+            BiometricKeyStore.createKeyIfNeeded()
+            val cipher = BiometricKeyStore.encryptCipher()
+            val bp = android.hardware.biometrics.BiometricPrompt.Builder(act)
+                .setTitle("请验证")
+                .setNegativeButton("取消", act.mainExecutor) { _, _ ->
+                    // onError("用户取消")
+                }
+                .build()
+            bp.authenticate(
+                android.hardware.biometrics.BiometricPrompt.CryptoObject(cipher),
+                CancellationSignal(),
+                act.mainExecutor,
+                object : android.hardware.biometrics.BiometricPrompt.AuthenticationCallback() {
+
+                    override fun onAuthenticationSucceeded(result: android.hardware.biometrics.BiometricPrompt.AuthenticationResult) {
+                        val cipher = result.cryptoObject?.cipher
+                        cipher?.doFinal(Storage.readToken().toByteArray())?.let {
+                            (it + cipher.iv).saveTokenIv()
+                        }
+                        // val  = BiometricKeyStore.decryptCipher(Storage.readTokenIv())
+                        // Log.d("xiaoming", "验证成功 ${String(Storage.readTokenIv())}")
+                    }
+
+                    override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
+                        Log.d("xiaoming", "验证失败")
+                    }
+                }
+            )
+        }
+    }
+
+}
+
+/**
+ * 指纹签名校验操作
+ */
+object BiometricKeyStore {
+
+    private const val KEY_ALIAS = "biometric_key"
+    private const val ANDROID_KEYSTORE = "AndroidKeyStore"
+
+    fun createKeyIfNeeded() {
+        val ks = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
+        if (ks.containsAlias(KEY_ALIAS)) return
+
+        val generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE)
+
+        val spec = KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
+            .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
+            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
+            .setUserAuthenticationRequired(true)
+            .apply {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+                    setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG)
+                } else {
+                    setUserAuthenticationValidityDurationSeconds(-1)
+                }
+            }
+            .build()
+
+        generator.init(spec)
+        generator.generateKey()
+    }
+
+    private fun getKey(): SecretKey {
+        val ks = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
+        return ks.getKey(KEY_ALIAS, null) as SecretKey
+    }
+
+    fun encryptCipher(): Cipher =
+        Cipher.getInstance("AES/CBC/PKCS7Padding").apply {
+            init(Cipher.ENCRYPT_MODE, getKey())
+        }
+
+    fun decryptCipher(iv: ByteArray): Cipher =
+        Cipher.getInstance("AES/CBC/PKCS7Padding").apply {
+            init(Cipher.DECRYPT_MODE, getKey(), IvParameterSpec(iv))
+        }
+}

+ 1 - 2
gradle/libs.versions.toml

@@ -30,8 +30,7 @@ androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-
 androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
 androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
 androidx-compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version = "2.8.0" }
-androidx-biometric = { group = "androidx.biometric", name = "biometric", version = "1.4.0-alpha05" }
-androidx-compose-biometric = { group = "androidx.biometric", name = "biometric-compose", version = "1.0.0-alpha02" }
+androidx-biometric = { group = "androidx.biometric", name = "biometric", version = "1.1.0" }
 
 # 网络请求
 retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version = "3.0.0" }