使用 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
如果您调用的 API 与访问令牌的 aud 不匹配,通常会出现错误 “InvalidAuthenticationToken,消息:访问令牌验证失败。受众无效”。
在您的场景中,您正在为自定义 API 生成访问令牌,但调用
userAdModel
API 因此会出现错误。
userAdModel
需要将 aud 声明设置为 https://graph.microsoft.com/
的访问令牌。注意:您无法为 Microsoft Graph 和自定义范围生成单个令牌。一个访问令牌只能包含一个 aud。因此,您必须分别生成两个访问令牌,一个用于调用
(Microsoft Graph 范围),另一个具有自定义范围的访问令牌用于调用自定义 API。UserAdModel
用于生成具有两个不同范围的两个访问令牌并调用两个不同 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}"),
),
),
],
),
),
),
),
);
}
}
代码可以生成两个不同的访问令牌:
kApiScopes
列表获取(例如,"api://xxx/.default"
)。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
如果您在一个请求中将范围作为
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。
第二个请求:范围为
api://xxx/access_as_user
并调用自定义API。