You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS设备Flutter InAppWebView微软AD登录SSL错误求助

解决iOS Flutter InAppWebView中微软AD登录的SSL错误

问题场景

集成微软AD登录的网页支持域内企业邮箱及外部邮箱登录,已开启浏览器弹窗权限。同一URL在iOS系统浏览器(Safari/Chrome)中可正常登录,但在Flutter InAppWebView中出现SSL错误,错误信息如下:

错误日志

flutter: }, sslError: SslError{code: UNSPECIFIED, message: Indicates the evaluation succeeded and the certificate is implicitly trusted, but user intent was not explicitly specified.}}}

当前实现代码

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';

class CustomInAppBrowser extends StatefulWidget {
  final String url;

  const CustomInAppBrowser({super.key, required this.url});

  @override
  State<CustomInAppBrowser> createState() => _CustomInAppBrowserState();
}

class _CustomInAppBrowserState extends State<CustomInAppBrowser> {
  final GlobalKey webViewKey = GlobalKey();

  String url = '';
  String title = '';
  double progress = 0;
  bool? isSecure;
  InAppWebViewController? webViewController;

  @override
  void initState() {
    super.initState();
    url = widget.url;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: <Widget>[
          Expanded(
            child: Stack(
              children: [
                InAppWebView(
                  key: webViewKey,
                  initialUrlRequest: URLRequest(url: WebUri(widget.url)),
                  onNavigationResponse: (controller, navigationResponse) async {
                    debugPrint('navigationResponse: ${navigationResponse}');
                    return NavigationResponseAction.ALLOW;
                  },
                  onPermissionRequest: (controller, permissionRequest) async {
                    debugPrint('permissionRequest: ${permissionRequest}');
                    return PermissionResponse.fromMap({
                      'resources': permissionRequest.resources,
                      'action': PermissionResponseAction.GRANT
                    });
                  },
                  onReceivedServerTrustAuthRequest: (InAppWebViewController controller,
                      URLAuthenticationChallenge challenge) async {
                    debugPrint('onReceivedServerTrustAuthRequest: ${challenge}');
                    return ServerTrustAuthResponse(action: ServerTrustAuthResponseAction.PROCEED);
                  },
                  initialSettings: InAppWebViewSettings(
                    transparentBackground: true,
                    safeBrowsingEnabled: true,
                    isFraudulentWebsiteWarningEnabled: true,
                  ),
                  onWebViewCreated: (controller) async {
                    webViewController = controller;
                    if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
                      await controller.startSafeBrowsing();
                    }
                  },
                  onLoadStart: (controller, url) {
                    if (url != null) {
                      setState(() {
                        this.url = url.toString();
                        isSecure = urlIsSecure(url);
                      });
                    }
                  },
                  onLoadStop: (controller, url) async {
                    if (url != null) {
                      setState(() {
                        this.url = url.toString();
                      });
                    }

                    final sslCertificate = await controller.getCertificate();
                    setState(() {
                      isSecure = sslCertificate != null || (url != null && urlIsSecure(url));
                    });
                  },
                  onUpdateVisitedHistory: (controller, url, isReload) {
                    if (url != null) {
                      setState(() {
                        this.url = url.toString();
                      });
                    }
                  },
                  onTitleChanged: (controller, title) {
                    if (title != null) {
                      setState(() {
                        this.title = title;
                      });
                    }
                  },
                  onProgressChanged: (controller, progress) {
                    setState(() {
                      this.progress = progress / 100;
                    });
                  },
                ),
                progress < 1.0 ? LinearProgressIndicator(value: progress) : Container(),
              ],
            ),
          ),
        ],
      ),
    );
  }

  void handleClick(int item) async {
    switch (item) {
      case 0:
        await InAppBrowser.openWithSystemBrowser(url: WebUri(url));
        break;
      case 1:
        await webViewController?.clearCache();
        if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
          await webViewController?.clearHistory();
        }
        setState(() {});
        break;
    }
  }

  static bool urlIsSecure(Uri url) {
    return (url.scheme == "https") || isLocalizedContent(url);
  }

  static bool isLocalizedContent(Uri url) {
    return (url.scheme == "file" ||
        url.scheme == "chrome" ||
        url.scheme == "data" ||
        url.scheme == "javascript" ||
        url.scheme == "about");
  }
}

已配置的Info.plist内容

<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key><true/>
</dict>

解决方案

1. 让InAppWebView使用系统证书信任策略

修改InAppWebViewSettings,添加serverTrustAuthenticationPolicy配置,让WebView和系统浏览器使用相同的证书信任规则:

initialSettings: InAppWebViewSettings(
  transparentBackground: true,
  safeBrowsingEnabled: true,
  isFraudulentWebsiteWarningEnabled: true,
  // 启用系统级证书信任
  serverTrustAuthenticationPolicy: ServerTrustAuthenticationPolicy.USE_DEVICE_SETTINGS,
),

该配置会让InAppWebView继承iOS系统的证书信任列表,若设备已信任企业CA证书(微软AD相关),WebView会自动信任对应域名的证书。

2. 优化ATS配置(替代全局允许)

全局开启NSAllowsArbitraryLoads存在安全风险,建议针对微软AD相关域名配置精确的ATS例外:

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <false/>
  <key>NSExceptionDomains</key>
  <dict>
    <!-- 替换为你的AD登录主域名 -->
    <key>your-ad-login-domain.com</key>
    <dict>
      <key>NSIncludesSubdomains</key>
      <true/>
      <key>NSTemporaryExceptionMinimumTLSVersion</key>
      <string>TLSv1.2</string>
    </dict>
    <!-- 添加微软AD授权相关域名 -->
    <key>login.microsoftonline.com</key>
    <dict>
      <key>NSIncludesSubdomains</key>
      <true/>
    </dict>
  </dict>
</dict>

3. 完善证书验证回调逻辑

若上述方案无效,可在onReceivedServerTrustAuthRequest中添加针对性的证书验证逻辑,确保仅信任目标域名的合法证书:

onReceivedServerTrustAuthRequest: (controller, challenge) async {
  final trustedDomains = ["your-ad-login-domain.com", "login.microsoftonline.com"];
  // 验证请求域名是否在信任列表中
  if (trustedDomains.contains(challenge.protectionSpace.host)) {
    return ServerTrustAuthResponse(action: ServerTrustAuthResponseAction.PROCEED);
  }
  // 非信任域名默认拒绝
  return ServerTrustAuthResponse(action: ServerTrustAuthResponseAction.CANCEL);
},

内容的提问来源于stack exchange,提问作者Secret Santa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 15:04:51