Flutter Web端替代InAppWebView实现SAML登录方案求助
Flutter Web端SAML身份认证替代方案
核心思路
由于flutter_inappwebview不支持Web平台,推荐使用官方维护的webview_flutter插件,它对Web端提供了基于iframe的实现,能适配你的SAML登录流程。
步骤1:添加依赖
在pubspec.yaml中替换原有依赖:
dependencies: flutter: sdk: flutter webview_flutter: ^4.4.0 webview_flutter_web: ^0.2.2+1 # Web端适配包
步骤2:修改登录页面代码
替换原有InAppWebView相关实现,适配Web端:
import 'dart:convert'; import 'package:feeta/services/TokenManager.dart'; import 'package:feeta/views/dashboard/MyDashboard.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:webview_flutter/webview_flutter.dart'; // Web端需要导入适配包 import 'package:webview_flutter_web/webview_flutter_web.dart'; class LoginWebView extends StatefulWidget { const LoginWebView({super.key}); @override State<LoginWebView> createState() => _LoginWebViewState(); } class _LoginWebViewState extends State<LoginWebView> { final tokenManager = TokenManager(); WebViewController? webViewController; bool isShowingError = false; final initialUrl = "https://accounts.google.com/o/saml2/initsso?idpid=C02qtgmcd&spid=859837136968&forceauthn=false"; @override void initState() { super.initState(); // 初始化Web端控制器 if (kIsWeb) { WebViewPlatform.instance = WebWebViewPlatform(); } // 创建控制器并配置核心参数 webViewController = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..loadRequest(Uri.parse(initialUrl)) ..setNavigationDelegate( NavigationDelegate( onProgress: onWebViewProgressChanged, ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Login"), ), body: Column( children: [ Expanded( child: WebViewWidget(controller: webViewController!), ), ], ), ); } void onWebViewProgressChanged(int progress) async { if (progress == 100 && webViewController != null) { String html = await getHtmlFromWebViewController(webViewController!); if (!isShowingError && html.contains('not_a_saml_app')) { isShowingError = true; showErrorDialog(); return; } String token = extractTokenFromHTML(html); if (kDebugMode) { print("My Token: $token"); } if (token.isNotEmpty) { tokenManager.storeToken(token); navigateToNewScreen(); } } } void showErrorDialog() { showDialog( context: context, barrierDismissible: false, builder: (context) { return AlertDialog( title: const Text("Login Error"), content: const Text( "Oops! It seems like you're trying to login with a personal or different email address. Please make sure to use your Lattice email ID for authentication."), actions: [ TextButton( child: const Text("Retry"), onPressed: () { Navigator.pop(context); isShowingError = false; logout(); }, ), ], ); }, ); } Future<String> getHtmlFromWebViewController(WebViewController controller) async { try { // Web端和移动端都支持该方法执行JS final result = await controller.runJavaScriptReturningResult( 'document.documentElement.outerHTML;'); return result.toString(); } catch (e) { if (kDebugMode) { print('Error retrieving HTML: $e'); } return ''; } } String extractTokenFromHTML(String html) { try { String jsonString = html.substring(html.indexOf('{'), html.lastIndexOf('}') + 1); Map<String, dynamic> json = jsonDecode(jsonString); String _token = json['token'] as String; return _token; } catch (e) { if (kDebugMode) { print('Error extracting token from HTML: $e'); } return ''; } } void logout() async { if (webViewController != null) { // 清除缓存并重新加载登录页 await webViewController!.clearCache(); await webViewController!.clearLocalStorage(); // 额外清除本地存储 webViewController!.loadRequest(Uri.parse(initialUrl)); } if (kDebugMode) { print("came back on this method"); } } void navigateToNewScreen() async { Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => const MyDashboard()), ); } }
关键注意事项
- 跨域限制:Web端如果SAML登录页面的
X-Frame-Options设置为DENY或SAMEORIGIN,会导致iframe无法加载。需要联系SAML服务提供商(如Google Workspace)配置允许你的Flutter Web域名嵌入,或者改用后端代理中转请求。 - JavaScript权限:必须开启
JavaScriptMode.unrestricted,否则无法执行获取HTML的脚本。 - 缓存处理:Web端可根据需求添加本地存储清除逻辑,避免残留登录状态。
备选方案:浏览器重定向模式
如果iframe嵌入遇到跨域问题无法解决,可采用浏览器重定向方式:
- 使用
url_launcher插件引导用户在外部浏览器打开SAML登录URL。 - SAML登录完成后,后端生成token并跳转回你的Flutter Web应用的回调URL(如
https://yourapp.com/login-callback?token=xxx)。 - 在Flutter Web中监听URL参数,提取token并完成登录流程。
这种方式需要后端配合处理SAML回调,但能彻底避免iframe的跨域限制。
内容的提问来源于stack exchange,提问作者Saurabh Jain
相关产品推荐
相关产品推荐

