Flutter Azure认证插件msal_auth_azure的使用

Flutter Azure认证插件msal_auth_azure的使用

请参考以下详细说明和示例代码,了解如何在Flutter应用中使用msal_auth_azure插件进行Azure认证。

版本 3.0.0

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

升级指南

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

  • 更新gradle到3.6.0或更高版本。
  • 更新Kotlin到1.4.31或更高版本(可能与1.4.0+的较低版本兼容,但未经过测试)。
  • 更新msal_default_config.json文件以使用此仓库中的最新版本。
  • 更新Flutter到最新版本。

版本 1.0.0+ 警告

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

不建议使用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 Wrapper Library for Flutter

请注意,该产品处于非常早期的Alpha发布阶段,可能会有变动和错误。

Microsoft Authentication Library Flutter Wrapper是一个使用MSAL库(适用于Android和iOS)的封装。当前只支持公共客户端应用程序功能,使用隐式工作流。 如果您有其他功能需求,请告知我们。

设置

要在库中使用MSAL Flutter,首先需要设置一个Azure AD B2C租户和移动客户端。详细说明可以在这里找到。

Flutter

在Flutter应用中导入msal_auth_azure包,将其添加到pubspec.yaml文件的依赖项列表中:

dependencies:
  msal_auth_azure: ^1.0.0+2

Android (Kotlin)

请确保使用Kotlin版本1.4.31或更高版本。要设置这一点,请转到应用的Android文件夹,打开build.gradle文件,并在buildscript:ext.kotlin_version下将版本更改为1.4.31或更高版本。

确保AndroidManifest.xml包含互联网权限
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
在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>
msal_default_config.json复制到应用的android/src/main/res/raw文件夹

默认情况下,重定向URL为msal<YOUR-CLIENT-ID>://auth,但如果选择了不同的重定向URL,请输入该URL。注意,重定向URL方案和主机组合必须是唯一的,并且如果更改了它们,也需要在第2步的活动意图过滤器中更改。

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

对于示例,请参见此处

最低SDK版本必须至少为21

如果从默认版本16的新Flutter应用开始,请在android > app > build.gradle文件中的android:defaultConfig > minSdkVersion下更改此设置。

iOS (Swift)

此部分主要复制并修改自官方iOS MSAL库GitHub仓库的步骤1。更多详情请访问该仓库。

在Info.plist文件中添加回调的URL方案

替换<YOUR-CLIENT-ID>为你Azure B2C应用的客户端ID。

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>msauth.[BUNDLE-ID]</string>
        </array>
    </dict>
</array>
添加LSApplicationQueriesSchemes以允许调用Microsoft Authenticator(用于身份验证代理)
<key>LSApplicationQueriesSchemes</key>
<array>
    <string>msauthv2</string>
    <string>msauthv3</string>
</array>
在Xcode中打开应用的iOS项目,在Runner应用中展开Keychain Sharing并添加键链组com.microsoft.adalcache
在AppDelegate.swift中导入MSAL库
import MSAL
在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)
}
故障排除

可能遇到最低iOS部署过低的错误。MSAL Flutter要求最低iOS版本为11.0。 要设置这一点,请在iOS文件夹根目录下的Podfile文件的第一行添加platform :ios, '11.0'

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

如何使用

在Flutter中导入包

import 'package:msal_auth_azure/msal_auth_azure.dart';

使用静态工厂方法创建公共客户端应用程序实例

使用默认权威机构:

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

指定权威机构:

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

如果权威机构为空,则将使用默认权威机构,由相关的MSAL库实现定义,默认是公共端点。

交互式获取令牌

调用acquireToken函数传递你希望获取令牌的范围。注意该函数会在失败时抛出异常,并应使用try-catch块包围。

不要包括默认添加的openiduser_impersonation范围。

try {
    String token = await pca.acquireToken(["https://msalfluttertest.onmicrosoft.com/msalbackend/user_impersonation"]);
} on MsalException {
    // 错误处理逻辑
}

静默获取令牌

一旦用户至少登录一次,可以调用acquireTokenSilent函数,传递你希望获取令牌的范围。注意该函数会在失败时抛出异常,并应使用try-catch块包围。

不要包括默认添加的openiduser_impersonation范围。

try {
    String token = await pca.acquireTokenSilent(["https://msalfluttertest.onmicrosoft.com/msalbackend/user_impersonation"]);
} on MsalException {
    // 错误处理逻辑
}

登出

调用logout方法。

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

可能抛出的异常列表

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

故障排除

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

示例代码

以下是完整的示例代码,展示了如何在Flutter应用中使用msal_auth_azure插件进行Azure认证。

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

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:msal_auth_azure/msal_auth_azure.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 Azure认证插件msal_auth_azure的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter Azure认证插件msal_auth_azure的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


msal_auth_azure 是一个用于在 Flutter 应用中实现 Azure Active Directory (Azure AD) 认证的插件。它基于微软的 MSAL (Microsoft Authentication Library) 库,允许开发者轻松地将 Azure AD 认证集成到 Flutter 应用中。

以下是使用 msal_auth_azure 插件的基本步骤:

1. 添加依赖

首先,在 pubspec.yaml 文件中添加 msal_auth_azure 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  msal_auth_azure: ^1.0.0  # 使用最新版本

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

2. 配置 Azure AD 应用

在 Azure 门户中创建一个应用程序注册,并获取以下信息:

  • 客户端 ID (Client ID): 这是你的应用程序的唯一标识符。
  • 租户 ID (Tenant ID): 这是你的 Azure AD 租户的唯一标识符。
  • 重定向 URI (Redirect URI): 这是认证成功后用户被重定向的 URI。

3. 初始化 MSAL 客户端

在你的 Flutter 应用中,初始化 MsalAuthAzure 客户端:

import 'package:msal_auth_azure/msal_auth_azure.dart';

final msalAuthAzure = MsalAuthAzure(
  clientId: 'YOUR_CLIENT_ID',
  tenantId: 'YOUR_TENANT_ID',
  redirectUri: 'YOUR_REDIRECT_URI',
);

4. 启动登录流程

使用 login() 方法来启动登录流程:

try {
  final result = await msalAuthAzure.login();
  print('Access Token: ${result.accessToken}');
  print('ID Token: ${result.idToken}');
} catch (e) {
  print('Login failed: $e');
}

5. 处理令牌

登录成功后,你可以获取 accessTokenidToken,并使用它们来访问受保护的资源或进行用户身份验证。

6. 注销

使用 logout() 方法来注销用户:

await msalAuthAzure.logout();

7. 处理静默登录

在某些情况下,你可能希望在不提示用户的情况下尝试静默登录。可以使用 acquireTokenSilent() 方法:

try {
  final result = await msalAuthAzure.acquireTokenSilent();
  print('Access Token: ${result.accessToken}');
} catch (e) {
  print('Silent login failed: $e');
}

8. 处理重定向 URI

在 Android 和 iOS 上,你需要配置重定向 URI 以处理认证回调。

Android

AndroidManifest.xml 中添加以下内容:

<activity android:name="com.azure.identity.msal.MsalActivity">
    <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_PATH" />
    </intent-filter>
</activity>

iOS

Info.plist 中添加以下内容:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>msauth.$(PRODUCT_BUNDLE_IDENTIFIER)</string>
        </array>
    </dict>
</array>
回到顶部