在构建机密客户端应用程序以后,可以获取应用的令牌,方法是调用 AcquireTokenForClient
,传递作用域,然后强制刷新令牌。
请求客户端凭据流时,其作用域是资源的名称后跟 /.default
。 这种表示法告知 Microsoft Entra ID 使用在应用程序注册过程中静态声明的应用程序级权限。 另外,这些 API 权限必须由租户管理员授予。
下面是将 Web API 的范围定义为 appsettings.json 文件中的部分配置的示例。 此示例摘自 GitHub 上的 .NET 控制台守护程序代码示例。
{
"AzureAd": {
// Same AzureAd section as before.
},
"MyWebApi": {
"BaseUrl": "https://localhost:44372/",
"RelativePath": "api/TodoList",
"RequestAppToken": true,
"Scopes": [ "[Enter here the scopes for your web API]" ]
}
}
final static String GRAPH_DEFAULT_SCOPE = "https://microsoftgraph.chinacloudapi.cn/.default";
const tokenRequest = {
scopes: [process.env.GRAPH_ENDPOINT + '.default'], // e.g. 'https://microsoftgraph.chinacloudapi.cn/.default'
};
在 MSAL Python 中,该配置文件应该如以下代码片段所示:
{
"scope": ["https://microsoftgraph.chinacloudapi.cn/.default"],
}
ResourceId = "someAppIDURI";
var scopes = new [] { ResourceId+"/.default"};
用于客户端凭据的作用域应该始终为资源 ID 后跟 /.default
。
重要
当 MSAL 为接受 1.0 版访问令牌的资源请求访问令牌时,Microsoft Entra ID 会获取最后一个斜杠前面的所有内容并将其用作资源标识符,从请求的范围内分析所需的受众。
因此,就像 Azure SQL 数据库 (https://database.chinacloudapi.cn
) 一样,如果资源需要以斜杠结尾的受众(对于 Azure SQL 数据库为 https://database.chinacloudapi.cn/
),你将需要请求作用域 https://database.chinacloudapi.cn//.default
。 (注意双斜杠。)另请参阅 MSAL.NET 问题 #747:Resource url's trailing slash is omitted, which caused sql auth failure
。
AcquireTokenForClient API
为了获取应用的令牌,请使用 AcquireTokenForClient
或其等效命令,具体取决于平台。
使用 Microsoft.Identity.Web 时,无需获取令牌。 可以使用更高级别的 API,如从守护程序应用程序调用一个 Web API中所示。 但是,如果使用的 SDK 需要令牌,以下代码片段则会显示如何获取此令牌。
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Web;
// In the Program.cs, acquire a token for your downstream API
var tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance();
ITokenAcquirer acquirer = tokenAcquirerFactory.GetTokenAcquirer();
AcquireTokenResult tokenResult = await acquirer.GetTokenForUserAsync(new[] { "https://microsoftgraph.chinacloudapi.cn/.default" });
string accessToken = tokenResult.AccessToken;
此代码摘自 MSAL Java 开发示例。
private static IAuthenticationResult acquireToken() throws Exception {
// Load token cache from file and initialize token cache aspect. The token cache will have
// dummy data, so the acquireTokenSilently call will fail.
TokenCacheAspect tokenCacheAspect = new TokenCacheAspect("sample_cache.json");
IClientCredential credential = ClientCredentialFactory.createFromSecret(CLIENT_SECRET);
ConfidentialClientApplication cca =
ConfidentialClientApplication
.builder(CLIENT_ID, credential)
.authority(AUTHORITY)
.setTokenCacheAccessAspect(tokenCacheAspect)
.build();
IAuthenticationResult result;
try {
SilentParameters silentParameters =
SilentParameters
.builder(SCOPE)
.build();
// try to acquire token silently. This call will fail since the token cache does not
// have a token for the application you are requesting an access token for
result = cca.acquireTokenSilently(silentParameters).join();
} catch (Exception ex) {
if (ex.getCause() instanceof MsalException) {
ClientCredentialParameters parameters =
ClientCredentialParameters
.builder(SCOPE)
.build();
// Try to acquire a token. If successful, you should see
// the token information printed out to console
result = cca.acquireToken(parameters).join();
} else {
// Handle other exceptions accordingly
throw ex;
}
}
return result;
}
以下代码片段说明了 MSAL 节点机密客户端应用程序中的令牌获取:
try {
const authResponse = await cca.acquireTokenByClientCredential(tokenRequest);
console.log(authResponse.accessToken) // display access token
} catch (error) {
console.log(error);
}
# The pattern to acquire a token looks like this.
result = None
# First, the code looks up a token from the cache.
# Because we're looking for a token for the current app, not for a user,
# use None for the account parameter.
result = app.acquire_token_silent(config["scope"], account=None)
if not result:
logging.info("No suitable token exists in cache. Let's get a new one from Azure AD.")
result = app.acquire_token_for_client(scopes=config["scope"])
if "access_token" in result:
# Call a protected API with the access token.
print(result["token_type"])
else:
print(result.get("error"))
print(result.get("error_description"))
print(result.get("correlation_id")) # You might need this when reporting a bug.
using Microsoft.Identity.Client;
// With client credentials flows, the scope is always of the shape "resource/.default" because the
// application permissions need to be set statically (in the portal or by PowerShell), and then granted by
// a tenant administrator.
string[] scopes = new string[] { "https://microsoftgraph.chinacloudapi.cn/.default" };
AuthenticationResult result = null;
try
{
result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
// The application doesn't have sufficient permissions.
// - Did you declare enough app permissions during app creation?
// - Did the tenant admin grant permissions to the application?
}
catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011"))
{
// Invalid scope. The scope has to be in the form "https://resourceurl/.default"
// Mitigation: Change the scope to be as expected.
}
AcquireTokenForClient 使用应用程序令牌缓存
在 MSAL.NET 中,AcquireTokenForClient
使用应用程序令牌缓存。 (所有其他 AcquireTokenXX 方法都使用用户令牌缓存。)不要在调用 AcquireTokenForClient
之前调用 AcquireTokenSilent
,因为 AcquireTokenSilent
使用“用户” 令牌缓存。 AcquireTokenForClient
会检查应用程序令牌缓存本身并对其进行更新。
如果还没有适用于所选语言的库,你可能希望直接使用协议:
POST /{tenant}/oauth2/v2.0/token HTTP/1.1 //Line breaks for clarity.
Host: login.partner.microsoftonline.cn
Content-Type: application/x-www-form-urlencoded
client_id=00001111-aaaa-2222-bbbb-3333cccc4444
&scope=https%3A%2F%2Fmicrosoftgraph.chinacloudapi.cn%2F.default
&client_secret=A1b-C2d_E3f.H4i,J5k?L6m!N7o-P8q_R9s.T0u
&grant_type=client_credentials
POST /{tenant}/oauth2/v2.0/token HTTP/1.1 // Line breaks for clarity.
Host: login.partner.microsoftonline.cn
Content-Type: application/x-www-form-urlencoded
scope=https%3A%2F%2Fmicrosoftgraph.chinacloudapi.cn%2F.default
&client_id=11112222-bbbb-3333-cccc-4444dddd5555
&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&client_assertion=aaaaaaaa-0b0b-...
&grant_type=client_credentials
有关详细信息,请参阅协议文档:Microsoft 标识平台和 OAuth 2.0 客户端凭据流。
你是否使用过 resource/.default 作用域?
如果出现一条错误消息,指出所使用的作用域无效,则表明你并未使用 resource/.default
作用域。
如果在调用 API 时出现错误“权限不足,无法完成该操作”, 则租户管理员需要授予对应用程序的权限。
如果未向应用程序授予管理员同意,将遇到以下错误:
Failed to call the web API: Forbidden
Content: {
"error": {
"code": "Authorization_RequestDenied",
"message": "Insufficient privileges to complete the operation.",
"innerError": {
"request-id": "<guid>",
"date": "<date>"
}
}
}
根据角色选择以下选项之一。
对于云应用程序管理员,请转到 Microsoft Entra 管理中心中的“企业应用程序”。 选择应用注册,然后从左侧窗格的“安全”部分中选择“权限”。 然后,选择标有“为 {租户名称} 授予管理员同意”的大按钮,(其中 {租户名称} 是你的目录的名称)。
对于租户的标准用户,可以请求云应用程序管理员为应用程序授予管理员同意。 为此,请为管理员提供以下 URL:
https://login.partner.microsoftonline.cn/Enter_the_Tenant_Id_Here/adminconsent?client_id=Enter_the_Application_Id_Here
在 URL 中:
- 将
Enter_the_Tenant_Id_Here
替换为租户 ID 或租户名称(例如,contoso.microsoft.com
)。
Enter_the_Application_Id_Here
是已注册的应用程序的应用程序(客户端)ID。
在使用上述 URL 为应用授予同意后,可能会显示错误 AADSTS50011: No reply address is registered for the application
。 之所以出现此错误,是因为此应用程序和 URL 没有重定向 URI。 这可予以忽视。
如果你的守护程序应用调用你自己的 Web API,并且你无法向守护程序的应用注册添加应用权限,则需要将应用角色添加到 Web API 的应用注册。