Flutter微软身份验证插件msal_flutter_tlmn的使用

Flutter 微软身份验证插件 msal_flutter_tlmn 的使用

版本 3.0.0

我们已经将代码库更新到 Dart 3,并更改了 iOS 刷新令牌处理。从原始库分叉并将其版本号更改为 3.0.0。当前使用的分支为 msal_flutter,但目前我们不使用它。

升级指南

要将旧项目升级到 V2,请确保执行以下操作:

  • 更新 Gradle 至 3.6.0+
  • 更新 Kotlin 至 1.4.31 或更高版本(较低版本可能可以工作,但未经过测试)
  • 更新 msal_default_config.json 文件至此仓库中的最新版本
  • 更新 Flutter 至最新版本

版本 1.0.0+ 警告

版本 1.0.0 使用更新后的 MSAL 库并迁移到 Android-X。1.0.0 不兼容旧版本。请仅在准备好迁移您的 Android 应用并更改构造函数调用方式时才更新到 1.0.+。版本 1+ 是在 iOS 13+ 上使用 MSAL 所必需的。

建议不要使用 login.microsoftonline.com 权限和端点,因为它们似乎正在被弃用且由于域相同导致无法区分保存的密码。 新的权限模板是:

"https://<tenant>.b2clogin.com/tfp/<tenant>.onmicrosoft.com/<user-flow>"

例如:

"https://msalfluttertest.b2clogin.com/tfp/msalfluttertest.onmicrosoft.com/B2C_1_sisu"

对于新构建中已知问题的排查,请滚动到底部,所有找到的错误和修复将在那里列出。

MSAL 包装库用于 Flutter

请注意,该产品处于非常早期的 alpha 阶段,可能会发生变化并且存在错误。

Microsoft Authentication Library Flutter Wrapper 是一个使用 MSAL 库为 Android 和 iOS 提供公共客户端应用功能的包装器。目前仅支持隐式工作流。

如果您有其他功能需求,请告知我。

设置

要使用 MSAL Flutter 在您的库中,请首先设置 Azure AD B2C 租户和移动客户端(如果尚未完成),详细的说明可以在以下链接中找到: https://docs.microsoft.com/en-us/azure/active-directory-b2c/

Flutter

在您的 Flutter 应用程序中导入 Msal Flutter 包,请将其添加到 pubspec.yaml 文件的依赖项列表中。

dependencies:
    msal_flutter: ^1.0.0+2

Android (Kotlin)

请确保您使用的是 Kotlin 版本 1.4.31 或更高版本。转到您的应用的 Android 文件夹,打开 build.gradle 文件,在 buildscript:ext.kotlin_version 下更改版本为 1.4.31 或更高版本。

步骤:

  1. 确保您的 AndroidManifest.xml 包含互联网权限

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    
  2. AndroidManifest.xml 文件中添加以下意图过滤器,替换占位符 <YOUR-CLIENT-ID> 为您 Azure B2C 应用程序的客户端 ID。

    <activity
        android:name="com.microsoft.identity.client.BrowserTabActivity">
        <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="msauth"
                android:host="<YOUR_PACKAGE_NAME>"
                android:path="/<YOUR_BASE64_ENCODED_PACKAGE_SIGNATURE>" />
        </intent-filter>
    </activity>
    
  3. 从这个仓库复制 msal_default_config.json(或者如果您知道怎么做就自己创建一个)并将其放置在您的 Flutter 应用的 android/src/main/res/raw 文件夹中。 默认/传统的重定向 URL 为 msal<YOUR-CLIENT-ID>://auth,但如果选择了不同的重定向 URL,请输入该 URL。注意,重定向 URL 方案和主机组合必须对您的应用程序唯一,如果更改了重定向 URL,也必须在步骤 2 中的活动意图过滤器中更改。

警告:不要将应用程序类型设置为单个,MSAL Flutter 包装器仅与新的多账户配置兼容。

示例可见于: https://github.com/moodio/msal-flutter/blob/develop/example/android/app/src/main/res/raw/msal_default_config.json

  1. 最低 SDK 版本必须至少为 21。如果您从默认为 16 的新 Flutter 应用开始,请在 android > app > build.gradle 文件中的 android:defaultConfig>minSdkVersion 下更改此设置。

iOS (Swift)

此部分主要复制并修改自官方 iOS MSAL 库 GitHub 存储库的第一步。访问存储库以获取更多详细信息。

步骤:

  1. 将您的回调 URL 方案添加到您的 Info.plist 文件中,替换占位符 <YOUR-CLIENT-ID> 为您 Azure B2C 应用程序的客户端 ID。

    <key>CFBundleURLTypes</key>
    <array>
        <dict>
            <key>CFBundleURLSchemes</key>
            <array>
                <string>msauth.[BUNDLE-ID]</string>
            </array>
        </dict>
    </array>
    
  2. 添加 LSApplicationQueriesSchemes 以便允许调用 Microsoft Authenticator(用于认证代理)

    <key>LSApplicationQueriesSchemes</key>
    <array>
        <string>msauthv2</string>
        <string>msauthv3</string>
    </array>
    
  3. 打开您的 iOS 项目的 Xcode,点击 Runner 应用以打开配置,然后在能力下扩展 Keychain 共享并添加键链组 com.microsoft.adalcache

  4. 在您的 AppDelegate.swift 中导入 MSAL 库,通过在文件顶部添加以下内容:

    import MSAL
    
  5. 向您的 AppDelegate 类添加以下函数

    override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {    
        guard let sourceApplication = options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String else {
            return false
        }  
        return MSALPublicClientApplication.handleMSALResponse(url, sourceApplication: sourceApplication)
    }
    
  6. 排查问题 可能会遇到一些错误,例如最低 iOS 部署版本太低。MSAL Flutter 需要最低 iOS 版本为 11.0。 要设置此版本,请在 Podfile 文件的第一行添加 platform :ios, '11.0',该文件位于您的 iOS 文件夹的根目录中。

当从旧版本的 MSAL Flutter 升级时,您可能还需要删除 Podfile.lock 文件,该文件也在 iOS 文件夹中。

如何使用

  1. 在 Flutter 中导入包

    import 'package:msal_flutter/msal_flutter.dart';
    
  2. 使用静态工厂方法 createPublicClientApplication 异步创建一个新的对象实例,通过提供您的客户端 ID 和可选的权限。 使用默认权限:

    var pca = await PublicClientApplication.createPublicClientApplication("YOUR-CLIENT-ID");
    

    指定权限:

    var pca = await PublicClientApplication.createPublicClientApplication("YOUR-CLIENT-ID", authority: "https://<tenant>.b2clogin.com/tfp/<tenant>.onmicrosoft.com/<user-flow>");
    

    如果权限为空,则将使用由相关 MSAL 库实现定义的默认权限。

  3. 若要交互式检索令牌,请调用 acquireToken 函数并传递您希望获取令牌的范围。请注意,此函数会在失败时抛出错误,并应使用 try-catch 块包围,如下面的示例所示 不要包括默认添加的 openiduser_impersonation 范围

    try{
       String token = await pca.acquireToken(["https://msalfluttertest.onmicrosoft.com/msalbackend/user_impersonation"]);
    } on MsalException {
       // 错误处理逻辑
    }
    
  4. 一旦用户至少登录过一次,可以通过调用 acquireTokenSilent 函数异步检索令牌,传递您希望获取令牌的范围。请注意,此函数会在失败时抛出错误,并应使用 try-catch 块包围,如下面的示例所示 不要包括默认添加的 openiduser_impersonation 范围

    try{
        String token = await pca.acquireTokenSilent(["https://msalfluttertest.onmicrosoft.com/msalbackend/user_impersonation"]);
    } on MsalException{
        // 错误处理逻辑
    }
    
  5. 要注销,请调用 logout 方法

    try{
        await pca.logout();
    } on MsalException{
        // 错误处理逻辑
    }
    

可能抛出的异常列表

异常 描述
MsalException 基础异常,继承自所有其他异常。用于一般或未知错误
MsalChangedClientIdException 尝试初始化具有不同客户端 ID 的第二个客户端 ID
MsalInitializationException 初始化客户端时出错。最可能是由于配置文件不正确
MsalInvalidConfigurationException 在设置公共客户端应用时出现配置错误,例如无效的客户端 ID 或权限
MsalInvalidScopeException 无效的范围或未提供任何范围。目前仅在 Android 中受支持
MsalNoAccountException 用户之前未登录、已注销或刷新令牌已过期,无法执行 acquireTokenSilent
MsalUninitializedException 在客户端初始化之前调用了客户端方法
MsalUserCancelledException 用户取消了登录请求。目前仅在 Android 中受支持,对于 iOS 则抛出 MsalException

故障排除

请注意,目前在使用稍旧版本 Kotlin 的 Android 上似乎存在一个问题。 如果您在尝试获取令牌时遇到类似“找不到静态成员 msalApp”的错误,请转到您的应用的 Android 文件夹,打开 build.gradle 文件,并在第二行将 Kotlin 版本从 1.3.10 更改为 1.3.50。有关更多信息,请参阅问题 #4。 修复将很快实施。

示例代码

import 'dart:async';
import 'dart:developer';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:msal_flutter_tlmn/msal_flutter_tlmn.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  [@override](/user/override)
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  static const String _authority =
      "https://msalfluttertest.b2clogin.com/tfp/3fab2993-1fec-4a8c-a6d8-2bfea01e64ea/B2C_1_phonesisu";
  static const String _iosRedirectUri =
      "msauth.com.muljin.msalflutterv2://auth";
  static const String _androidRedirectUri =
      "msauth://uk.co.moodio.msal_flutter_example/TvkGQnk1ERb%2Bl9pB4OeyeWrYmqo%3D";
  static const String _clientId = "fc6136e7-43d1-489c-b221-630e9e4402d3";
  static const List<String> _scopes = [
    "https://msalfluttertest.onmicrosoft.com/msaltesterapi/All"
  ];
  String _output = 'NONE';
  final config = MSALPublicClientApplicationConfig(
    androidRedirectUri: _androidRedirectUri,
    iosRedirectUri: _iosRedirectUri,
    clientId: _clientId,
    androidConfig: MSALAndroidConfig(
        authorities: [Authority(authorityUrl: Uri.parse(_authority))]),
    authority: Uri.parse(_authority),
  );

  MSALPublicClientApplication? pca;
  List<MSALAccount>? accounts;

  Future<void> _acquireToken() async {
    print("called acquiretoken");
    // 创建 PCA 如果尚未创建
    if (pca == null) {
      print("creating pca...");
      pca = await MSALPublicClientApplication.createPublicClientApplication(config);
      await pca!.initWebViewParams(MSALWebviewParameters());
    }

    print("pca created");

    String res = '';
    try {
      MSALResult? resp = await pca!
          .acquireToken(MSALInteractiveTokenParameters(scopes: _scopes));
      res = resp?.account.identifier ?? 'noAuth';
    } on MsalUserCancelledException {
      res = "User cancelled";
    } on MsalNoAccountException {
      res = "no account";
    } on MsalInvalidConfigurationException {
      res = "invalid config";
    } on MsalInvalidScopeException {
      res = "Invalid scope";
    } on MsalException {
      res = "Error getting token. Unspecified reason";
    }

    setState(() {
      _output = res;
    });
  }

  Future<void> _loadAccount() async {
    if (pca == null) {
      print("initializing pca");
      pca = await MSALPublicClientApplication.createPublicClientApplication(config);
      await pca!.initWebViewParams(MSALWebviewParameters());
    }
    try {
      final result = await pca!.loadAccounts();
      if (result != null) {
        accounts = result;
      }
    } catch (e) {
      log(e.toString());
    }
    setState(() {});
  }

  Future<void> _acquireTokenSilently() async {
    if (pca == null) {
      print("initializing pca");
      pca = await MSALPublicClientApplication.createPublicClientApplication(config);
      await pca!.initWebViewParams(MSALWebviewParameters());
    }

    String res = 'res';
    try {
      final response = await pca!.acquireTokenSilent(
          MSALSilentTokenParameters(
            scopes: _scopes,
          ),
          accounts?.isEmpty == true ? null : accounts?.first);
      res = response?.account.identifier ?? '';
    } on MsalUserCancelledException {
      res = "User cancelled";
    } on MsalNoAccountException {
      res = "no account";
    } on MsalInvalidConfigurationException {
      res = "invalid config";
    } on MsalInvalidScopeException {
      res = "Invalid scope";
    } on MsalException {
      res = "Error getting token silently!";
    }

    print("Got token");
    print(res);

    setState(() {
      _output = res;
    });
  }

  Future _logout() async {
    print("called logout");
    if (pca == null) {
      print("initializing pca");
      pca = await MSALPublicClientApplication.createPublicClientApplication(config);
      await pca!.initWebViewParams(MSALWebviewParameters());
    }

    print("pca is not null");
    String res;
    try {
      if (accounts?.isNotEmpty == true) {
        await pca!.logout(MSALSignoutParameters(), accounts!.first);
      }
      res = "Account removed";
    } on MsalException {
      res = "Error signing out";
    } on PlatformException catch (e) {
      res = "some other exception ${e.toString()}";
    }

    print("setting state");
    setState(() {
      _output = res;
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: Column(
            children: <Widget>[
              ElevatedButton(
                onPressed: _acquireToken,
                child: Text('AcquireToken()'),
              ),
              ElevatedButton(
                  onPressed: _loadAccount, child: Text('loadAccount()')),
              ElevatedButton(
                  onPressed: _acquireTokenSilently,
                  child: Text('AcquireTokenSilently()')),
              ElevatedButton(onPressed: _logout, child: Text('Logout')),
              Text(_output),
              Expanded(
                  child: ListView.builder(
                itemCount: accounts?.length ?? 0,
                itemBuilder: (context, index) {
                  final item = accounts![index];
                  return ListTile(
                    title: Text(item.username ?? item.identifier),
                  );
                },
              ))
            ],
          ),
        ),
      ),
    );
  }
}

更多关于Flutter微软身份验证插件msal_flutter_tlmn的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter微软身份验证插件msal_flutter_tlmn的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何使用 msal_flutter_tlmn 插件进行微软身份验证的示例代码。这个插件允许Flutter应用通过Microsoft Authentication Library (MSAL) 进行身份验证。

首先,确保你已经在你的 pubspec.yaml 文件中添加了 msal_flutter_tlmn 依赖:

dependencies:
  flutter:
    sdk: flutter
  msal_flutter_tlmn: ^latest_version  # 请替换为最新版本号

然后,运行 flutter pub get 来获取依赖。

接下来是具体的代码示例:

1. 配置 MSAL

在你的 Flutter 应用中,创建一个配置类来存储你的 MSAL 配置信息。这通常包括客户端ID、重定向URI等。

import 'package:msal_flutter_tlmn/msal_flutter_tlmn.dart';

class MSALConfig {
  static final String clientId = "your-client-id"; // 替换为你的客户端ID
  static final List<String> redirectUris = ["msauth.yourpackage.name://auth"]; // 替换为你的重定向URI
  static final String authority = "https://login.microsoftonline.com/your-tenant-id"; // 替换为你的租户ID或组织ID
}

2. 初始化 MSAL

在你的应用启动时,初始化 MSAL。

import 'package:flutter/material.dart';
import 'package:msal_flutter_tlmn/msal_flutter_tlmn.dart';
import 'msal_config.dart'; // 假设你将配置类放在这个文件中

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Microsoft Authentication Example'),
        ),
        body: MSALInitializer(
          clientId: MSALConfig.clientId,
          redirectUris: MSALConfig.redirectUris,
          authority: MSALConfig.authority,
          onInitialized: (PublicClientApplication client) {
            // 初始化成功后,你可以在这里保存client实例以进行后续操作
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => AuthScreen(client: client)),
            );
          },
          onError: (String errorMessage, Exception exception) {
            // 处理初始化错误
            print("MSAL Initialization Error: $errorMessage");
          },
        ),
      ),
    );
  }
}

3. 执行身份验证

在你的 AuthScreen 中,添加一个按钮来触发身份验证流程。

import 'package:flutter/material.dart';
import 'package:msal_flutter_tlmn/msal_flutter_tlmn.dart';

class AuthScreen extends StatefulWidget {
  final PublicClientApplication client;

  AuthScreen({required this.client});

  @override
  _AuthScreenState createState() => _AuthScreenState();
}

class _AuthScreenState extends State<AuthScreen> {
  late String resultText = "Not Authenticated";

  void _authenticate() async {
    try {
      var result = await widget.client.acquireTokenInteractive(
        scopes: ["User.Read"], // 请求的权限范围
      );
      setState(() {
        resultText = "Authenticated! Access Token: ${result.accessToken}";
      });
    } catch (e) {
      setState(() {
        resultText = "Authentication Failed: ${e.message}";
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Authenticate with Microsoft'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(resultText),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _authenticate,
              child: Text('Authenticate'),
            ),
          ],
        ),
      ),
    );
  }
}

4. 处理重定向URI

在你的 AndroidManifest.xmlInfo.plist 中添加相应的配置来处理重定向URI。

Android (AndroidManifest.xml)

<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="msauth.yourpackage.name" android:host="auth" />
</intent-filter>

iOS (Info.plist)

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>msauth.yourpackage.name</string>
        </array>
    </dict>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
    <string>msauthv2</string>
    <string>msauthv3</string>
</array>

以上代码展示了如何使用 msal_flutter_tlmn 插件在Flutter应用中实现微软身份验证。请确保替换示例代码中的占位符(如 your-client-idyour-tenant-id)为你的实际值。

回到顶部