Flutter azure_ad_authentication 失败抛出 InvalidAuthenticaation、AccessToken 验证失败,使用自定义范围

问题描述 投票:0回答:1

使用 Flutter 'package:azure_ad_authentication/azure_ad_authentication.dart'

尝试对 flutter 应用程序进行身份验证,但抛出无效令牌和令牌失败、无效受众

https://pub.dev/packages/azure_ad_authentication/example

从上面的链接,使用示例代码,我将范围更改为 AccessAsUser 同样在 Azure 中,使用 api:///AccessAsUser 定义范围,并在 Api 权限部分授予权限。

我可以成功登录flutter应用程序,但在控制台中它会抛出错误“[log] Msal Error:401 {error:{code:InvalidAuthenticationToken,message:访问令牌验证失败。无效的受众。,innerError:”

我可以生成令牌并对其进行解码 使用 jwt.io 我可以看到令牌的详细信息,但令牌显示“无效签名”,并且我在令牌中看不到任何范围 scp:“AccessASUser”。

但是当我在控制台中解码令牌时,我可以看到令牌的所有详细信息 aud 匹配,scp 匹配,用户名,电子邮件等..

注意:我没有使用任何图形范围或图形 API 调用 所有 azure 详细信息均匹配(权限、redirecturi、clientid)

我在 azure 中有自己的 Api,它使用 accesstoken

flutter azure authentication access-token
1个回答
0
投票

如果您调用的 API 与访问令牌的 aud 不匹配,通常会出现错误 “InvalidAuthenticationToken,消息:访问令牌验证失败。受众无效”

在您的场景中,您正在为自定义 API 生成访问令牌,但调用

userAdModel
API 因此会出现错误。

  • 访问用户详细信息或与 Microsoft Graph 交互时,
    userAdModel
    需要将 aud 声明设置为
    https://graph.microsoft.com/
    的访问令牌。
  • 如果您想调用自定义 API,请生成具有自定义 API 范围的不同访问令牌。

注意:您无法为 Microsoft Graph 和自定义范围生成单个令牌。一个访问令牌只能包含一个 aud。因此,您必须分别生成两个访问令牌,一个用于调用

UserAdModel
(Microsoft Graph 范围),另一个具有自定义范围的访问令牌用于调用自定义 API。

用于生成具有两个不同范围的两个访问令牌并调用两个不同 API(自定义和 Microsoft Graph)的示例代码:

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

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

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

class _MyAppState extends State<MyApp> {
  static const String _authority =
      "https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize";
  static const String _redirectUriMacos = "msauth.msalxxxx://auth";
  // static const String _redirectUriIos = "msalxxx";

  static const String _clientId = "ClientID";  // Your app's client ID
  static const String _tenantId = "<your-tenant-id>"; // Your Azure AD tenant ID

  String _output = 'NONE';
  
  // Scopes for your custom API and Microsoft Graph API
  static const List<String> kApiScopes = [
    "api://xxx/.default",  // Custom API scope
  ];

  static const List<String> kGraphScopes = [
    "https://graph.microsoft.com/user.read",  // Microsoft Graph scope for user data
    "https://graph.microsoft.com/Calendars.ReadWrite",  // If you need calendar access
  ];

  Future<void> _acquireTokenForAPI() async {
    await getResult(scopes: kApiScopes, isAcquireToken: true);
  }

  Future<void> _acquireTokenForGraph() async {
    await getResult(scopes: kGraphScopes, isAcquireToken: true);
  }

  Future<void> _acquireTokenSilentlyForAPI() async {
    await getResult(scopes: kApiScopes, isAcquireToken: false);
  }

  Future<void> _acquireTokenSilentlyForGraph() async {
    await getResult(scopes: kGraphScopes, isAcquireToken: false);
  }

  /// Example: return "{accessToken": xxx, "expiresOn": xxx}"
  Future<String> tokenString({required List<String> scopes}) async {
    AzureAdAuthentication pca = await intPca();
    return await pca.acquireTokenString(scopes: scopes);
  }

  Future<String> getResult({required List<String> scopes, bool isAcquireToken = true}) async {
    AzureAdAuthentication pca = await intPca();
    String? res;
    UserAdModel? userAdModel;
    try {
      if (isAcquireToken) {
        // Acquire token interactively (using the provided scopes)
        userAdModel = await pca.acquireToken(scopes: scopes);
      } else {
        // Acquire token silently (if the user has already granted permissions)
        userAdModel = await pca.acquireTokenSilent(scopes: scopes);
      }
    } on MsalUserCancelledException {
      res = "User cancelled";
    } on MsalNoAccountException {
      res = "No account found";
    } on MsalInvalidConfigurationException {
      res = "Invalid configuration";
    } on MsalInvalidScopeException {
      res = "Invalid scope";
    } on MsalException {
      res = "Error getting token. Unspecified reason";
    }

    setState(() {
      _output = (userAdModel?.toJson().toString() ?? res)!;
    });
    return (userAdModel?.toJson().toString() ?? res)!;
  }

  Future<AzureAdAuthentication> intPca() async {
    var _redirectUri = Platform.isIOS ? null : _redirectUriMacos;
    return await AzureAdAuthentication.createPublicClientApplication(
      clientId: _clientId, authority: _authority, redirectUri: _redirectUri,
    );
  }

  Future _logout() async {
    AzureAdAuthentication pca = await intPca();
    String res;
    try {
      await pca.logout();
      res = "Account removed";
    } on MsalException {
      res = "Error signing out";
    } on PlatformException catch (e) {
      res = "Some other exception: ${e.toString()}";
    }

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: SingleChildScrollView(
          child: Center(
            child: Column(
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: ElevatedButton(
                    onPressed: _acquireTokenForAPI,
                    child: const Text('AcquireToken for API'),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: ElevatedButton(
                    onPressed: _acquireTokenForGraph,
                    child: const Text('AcquireToken for Graph'),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: ElevatedButton(
                    onPressed: _acquireTokenSilentlyForAPI,
                    child: const Text('AcquireToken Silently for API'),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: ElevatedButton(
                    onPressed: _acquireTokenSilentlyForGraph,
                    child: const Text('AcquireToken Silently for Graph'),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: ElevatedButton(
                    onPressed: _logout,
                    child: const Text('Logout'),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Text(
                    _output
                        .replaceAll(",", ",\n")
                        .replaceAll("{", "{\n")
                        .replaceAll("}", "\n}"),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

代码可以生成两个不同的访问令牌

  1. 一个用于您的自定义 API:使用
    kApiScopes
    列表获取(例如,
    "api://xxx/.default"
    )。
  2. 一个用于 Microsoft Graph:使用
    kGraphScopes
    列表获取(例如,
    "https://graph.microsoft.com/user.read"
    "https://graph.microsoft.com/Calendars.ReadWrite"
    )。

根据用户按下的按钮(对于自定义 API 或 Microsoft Graph)获取令牌,并且它们作为每个范围的单独令牌返回。

https://login.microsoftonline.com/TenantID/oauth2/v2.0/token

client_id : ClientID
grant_type : authorization_code
scope : https://graph.microsoft.com/.default api://xxx/access_as_user
redirect_uri : XXX
code : code
client_secret : XXX

enter image description here

如果您在一个请求中将范围作为

https://graph.microsoft.com/.default api://xxx/access_as_user
传递以生成令牌,那么您将收到错误,因为范围无法与特定于资源的范围相结合。

因此 要解决该错误,您需要使用两个请求生成两个不同的访问令牌,一个请求的范围为

https://graph.microsoft.com/.default
,另一个访问令牌的范围为
api://xxx/access_as_user

第一个请求: 范围为

https://graph.microsoft.com/.default
并调用
userAdModel
API。

enter image description here

第二个请求:范围为

api://xxx/access_as_user
并调用自定义API。

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.