Flutter集成Keycloak(OpenID)内部WebView重定向问题求助
问题根源
你遇到的问题核心是url_launcher的inAppWebView模式无法拦截自定义Scheme的重定向请求。当Keycloak登录完成后跳转到你配置的自定义redirectUri(比如myapp://auth/callback)时,WebView无法识别并处理这个Scheme,导致跳转到异常页面。
解决方案:用webview_flutter替代url_launcher
webview_flutter支持拦截所有URL跳转,能捕获Keycloak的重定向请求,提取授权码后完成Token交换。
1. 更新依赖
修改pubspec.yaml,替换原有依赖并添加webview_flutter:
dependencies: flutter: sdk: flutter openid_client: ^0.4.6 webview_flutter: ^4.4.0
2. 配置自定义Scheme的跳转权限
Android端(android/app/src/main/AndroidManifest.xml)
在主<activity>标签内添加intent-filter,允许应用处理自定义Scheme:
<intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="myapp" android:host="auth" android:path="/callback" /> </intent-filter>
iOS端(ios/Runner/Info.plist)
添加以下配置,注册自定义Scheme:
<key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLSchemes</key> <array> <string>myapp</string> </array> <key>CFBundleURLName</key> <string>AuthCallback</string> </dict> </array>
3. 实现带拦截逻辑的WebView认证页面
创建一个专门的认证WebView页面,拦截重定向请求并提取授权码:
import 'package:flutter/material.dart'; import 'package:webview_flutter/webview_flutter.dart'; import 'package:openid_client/openid_client.dart'; class AuthWebViewPage extends StatefulWidget { final AuthorizationRequest authRequest; final Uri redirectUri; const AuthWebViewPage({ super.key, required this.authRequest, required this.redirectUri, }); @override State<AuthWebViewPage> createState() => _AuthWebViewPageState(); } class _AuthWebViewPageState extends State<AuthWebViewPage> { late WebViewController _webViewController; @override void initState() { super.initState(); _initWebViewController(); } void _initWebViewController() { _webViewController = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate( NavigationDelegate( onNavigationRequest: (NavigationRequest request) async { final uri = Uri.parse(request.url); // 拦截目标重定向地址 if (uri.scheme == widget.redirectUri.scheme && uri.host == widget.redirectUri.host && uri.path == widget.redirectUri.path) { final code = uri.queryParameters['code']; if (code != null) { // 关闭WebView,返回授权码 Navigator.pop(context, code); } return NavigationDecision.prevent; } return NavigationDecision.navigate; }, ), ) ..loadRequest(widget.authRequest.requestUri); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('用户登录')), body: WebViewWidget(controller: _webViewController), ); } }
4. 发起认证流程并交换Token
替换原有的认证逻辑,通过WebView获取授权码后,用openid_client交换Token:
// 初始化Keycloak OpenID客户端 final issuer = await Issuer.discover(Uri.parse('https://你的Keycloak域名/auth/realms/你的Realm')); final client = Client(issuer, '你的客户端ID'); // 配置重定向URI(需和Keycloak后台保持一致) const redirectUrl = 'myapp://auth/callback'; final redirectUri = Uri.parse(redirectUrl); // 创建授权请求 final authRequest = client.createAuthorizationRequest( scopes: ['openid', 'profile', 'email'], redirectUri: redirectUri, ); // 打开WebView页面,等待返回授权码 final code = await Navigator.push<String>( context, MaterialPageRoute( builder: (context) => AuthWebViewPage( authRequest: authRequest, redirectUri: redirectUri, ), ), ); if (code != null) { // 用授权码交换Token final tokenResponse = await authRequest.exchange(code); print('TOKEN = ${tokenResponse.accessToken}'); } else { print('认证流程被取消或失败'); }
5. Keycloak后台配置
确保Keycloak客户端的Valid Redirect URIs列表中包含你配置的自定义Scheme地址,比如myapp://auth/callback。
为什么之前的方案不行?
url_launcher的inAppWebView模式仅封装了基础WebView功能,没有提供拦截自定义Scheme跳转的能力。当Keycloak跳转到自定义Scheme时,WebView无法处理,只能尝试调用外部应用打开,最终导致异常页面。而webview_flutter通过NavigationDelegate可以完全控制URL跳转逻辑,完美解决这个问题。
内容的提问来源于stack exchange,提问作者Nipun

