Kotlin Multiplatform:如何提取WebView中SAML认证返回的JSON值?
问题描述
我使用WebView加载Google SAML身份验证URL完成用户登录,成功认证后WebView会返回如下JSON响应:
{ "status": 1, "msg": "user Authenticated !!", "user": { "userFirstName": "John", "userLastName": "Doe", "userEmail": "john@example.in", "userType": "manager", "manager": true, "employee": false, "teamLead": false }, "token": "eyJhbGciOiJI................." }
我需要从WebView中提取这些信息并进行本地存储。
我在Kotlin Multiplatform中使用compose-webview-multiplatform库实现WebView,当前代码如下:
@Composable internal fun LoginWebView(navHostController: NavHostController? = null) { val state = rememberWebViewState(url = SAML_DEV) val navigator = rememberWebViewNavigator() val jsBridge = rememberWebViewJsBridge() jsBridge.register(LoginMessageHandler()) state.webSettings.apply { isJavaScriptEnabled = true } DisposableEffect(Unit) { state.webSettings.apply { logSeverity = KLogSeverity.Verbose } onDispose { } } MaterialTheme { Column { TopAppBar( title = { Text(text = "Login") }, navigationIcon = { IconButton(onClick = { if (navigator.canGoBack) { navigator.navigateBack() } else { navHostController?.popBackStack() } }) { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back", ) } }, ) val loadingState = state.loadingState if (loadingState is LoadingState.Loading) { LinearProgressIndicator( color = MaterialTheme.colorScheme.onPrimary, modifier = Modifier .height(6.dp) .fillMaxWidth(), ) } WebView( state = state, modifier = Modifier.fillMaxSize(), navigator = navigator, webViewJsBridge = jsBridge, ) LaunchedEffect(state, navigator) { snapshotFlow { state.loadingState } .filter { it is LoadingState.Finished } .collect { navigator.evaluateJavaScript( """ try { // Select the <pre> element in the DOM const preElement = document.querySelector('pre'); if (preElement) { // Extract the JSON content from the <pre> element const jsonResponse = preElement.innerText; console.log("Extracted JSON response:", jsonResponse); // Send the response to the native side using the bridge window.JsBridge.postMessage('login', jsonResponse); } else { console.error("<pre> element not found"); } } catch (e) { console.error('Error extracting JSON response:', e); } """.trimIndent() ) } } } } } class LoginMessageHandler : IJsMessageHandler { override fun handle( message: JsMessage, navigator: WebViewNavigator?, callback: (String) -> Unit ) { println("LoginMessageHandler handle: $message") Logger.i { "Greet Handler Get Message: $message" } //val param = processParams<User>(message) callback(message.params) } override fun methodName(): String { return "login" } }
解决方案
1. 定义JSON对应的数据类
首先创建与返回JSON结构匹配的可序列化数据类,方便后续解析:
import kotlinx.serialization.Serializable @Serializable data class AuthResponse( val status: Int, val msg: String, val user: UserInfo, val token: String ) @Serializable data class UserInfo( val userFirstName: String, val userLastName: String, val userEmail: String, val userType: String, val manager: Boolean, val employee: Boolean, val teamLead: Boolean )
2. 完善消息处理器的解析与存储逻辑
修改LoginMessageHandler,将收到的JSON字符串解析为数据类,再执行本地存储操作:
import kotlinx.serialization.json.Json class LoginMessageHandler : IJsMessageHandler { // 配置JSON解析器,忽略未知字段避免解析失败 private val jsonParser = Json { ignoreUnknownKeys = true } override fun handle( message: JsMessage, navigator: WebViewNavigator?, callback: (String) -> Unit ) { try { // 将JSON字符串解析为AuthResponse对象 val authData = jsonParser.decodeFromString<AuthResponse>(message.params) // 执行本地存储 persistAuthData(authData) Logger.i { "登录信息已成功解析并存储:${authData.user.userEmail}" } callback("success") } catch (e: Exception) { Logger.e { "解析登录响应失败:${e.message}" } callback("error: ${e.message.orEmpty()}") } } override fun methodName(): String { return "login" } // 本地存储实现(根据项目选择KMP兼容的存储方案) private fun persistAuthData(authData: AuthResponse) { // 示例:使用MMKV(多平台高性能键值存储) // val mmkv = MMKV.defaultMMKV() // mmkv.encode("auth_token", authData.token) // mmkv.encode("user_profile", jsonParser.encodeToString(authData.user)) // 示例:使用Multiplatform DataStore // val dataStore = context.createDataStore<AuthResponse>("auth_store") // dataStore.updateData { authData } // 请根据项目实际依赖替换为对应存储逻辑 } }
3. 优化JS注入时机
原代码中每次页面加载完成都会执行提取逻辑,容易重复触发。建议增加URL判断,仅在登录成功的回调页面执行:
LaunchedEffect(state, navigator) { snapshotFlow { Pair(state.loadingState, state.content.getCurrentUrl()) } .filter { (loadingState, url) -> // 替换为实际的登录成功回调URL的特征关键词 loadingState is LoadingState.Finished && url?.contains("auth-callback") == true } .collect { navigator.evaluateJavaScript( """ try { const preElement = document.querySelector('pre'); if (preElement) { const jsonResponse = preElement.innerText; console.log("Extracted JSON response:", jsonResponse); window.JsBridge.postMessage('login', jsonResponse); } else { console.error("pre元素未找到"); } } catch (e) { console.error('提取JSON响应出错:', e); } """.trimIndent() ) } }
4. 添加依赖配置
若使用kotlinx.serialization解析JSON,需在项目中添加对应依赖:
- 根目录
build.gradle.kts添加插件:
plugins { kotlin("plugin.serialization") version "1.9.20" // 匹配你的Kotlin版本 }
- 模块
build.gradle.kts添加依赖:
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
5. 选择合适的KMP本地存储方案
推荐几种Kotlin Multiplatform兼容的存储方案:
- MMKV:腾讯开源的高性能键值存储,集成简单,支持多平台
- Multiplatform DataStore:基于Flow的结构化存储,适合小量配置/用户数据
- SQLDelight:关系型数据库,适合存储大量结构化数据
根据项目需求选择对应方案,完成persistAuthData方法的实现即可。
内容的提问来源于stack exchange,提问作者Cypher
相关产品推荐
相关产品推荐

