使用用户分配的身份时,.NetCore 2.2 API无法从AAD获取令牌

时间:2019-01-17 13:04:59

标签: azure azure-active-directory azure-sql-database

使用用户分配的托管身份时,我们无法从Azure App Service中以azure查询sql数据库(如果使用系统分配的托管身份,则可以正常工作)

该应用程序是.net core 2.2 Web api应用程序。

我们为Azure应用服务设置了一个用户分配的身份。

已使用以下命令将此身份设置为ad sql admin:

az sql server ad-admin create --resource-group iactests --server iactestsql --object-id -u iactestmanagedIdentity

令牌生成如下:

services.AddDbContext<SchoolContext>(options => options.UseSqlServer(new 
SqlConnection
        {
            ConnectionString = configuration.GetConnectionString("SchoolContext"),
            AccessToken = isDevelopmentEnvironment ? null : new AzureServiceTokenProvider().GetAccessTokenAsync("https://database.windows.net/").Result
        }), ServiceLifetime.Scoped);

这是我们得到的错误:

    Microsoft.Azure.Services.AppAuthentication.AzureServiceTokenProviderException: Parameters: Connection String: [No connection string specified], Resource: https://database.windows.net/, Authority: . Exception Message: Tried the following 3 methods to get an access token, but none of them worked.
Parameters: Connection String: [No connection string specified], Resource: https://database.windows.net/, Authority: . Exception Message: Tried to get token using Managed Service Identity. Access token could not be acquired. MSI ResponseCode: BadRequest, Response: 
Parameters: Connection String: [No connection string specified], Resource: https://database.windows.net/, Authority: . Exception Message: Tried to get token using Visual Studio. Access token could not be acquired. Visual Studio Token provider file not found at "D:\local\LocalAppData\.IdentityService\AzureServiceAuth\tokenprovider.json"
Parameters: Connection String: [No connection string specified], Resource: https://database.windows.net/, Authority: . Exception Message: Tried to get token using Azure CLI. Access token could not be acquired. 'az' is not recognized as an internal or external command,
operable program or batch file.


at Microsoft.Azure.Services.AppAuthentication.AzureServiceTokenProvider.GetAuthResultAsyncImpl(String authority, String resource, String scope)
at Microsoft.Azure.Services.AppAuthentication.AzureServiceTokenProvider.GetAuthenticationResultAsync(String resource, String tenantId)
at Microsoft.Azure.Services.AppAuthentication.AzureServiceTokenProvider.GetAccessTokenAsync(String resource, String tenantId)
--- End of inner exception stack trace ---

如果我们使用系统分配身份并将sql ad admin配置为该身份,那么它将正常工作

有什么想法吗?

预先感谢

2 个答案:

答案 0 :(得分:3)

AppAuthentication库现在支持从1.2.0-preview2 release开始为Azure VM和App Services指定用户分配的身份。

要使用用户分配的身份,您将需要设置以下格式的AppAuthentication连接字符串:

RunAs=App;AppId={ClientId of user-assigned identity}

AppAuthentication连接字符串可以设置为传递给AzureServiceTokenProvider构造函数的参数,也可以在 AzureServicesAuthConnectionString 环境变量中指定。有关AppAuthentication连接字符串的更多信息,请参见here

答案 1 :(得分:2)

至少在目前,AzureServiceTokenProvider似乎不支持用户分配的托管身份。 AzureServiceTokenProvder是对本地HTTP终结点的包装,该终结点为应用程序提供令牌。

我一直在调查,看来您必须向端点提供用户分配的托管身份的clientId才能获得令牌。而且AzureServiceTokenProvider没有办法做到这一点(至少我能弄清楚)。

用户分配的托管身份增加了为应用程序分配多个用户分配的托管身份的功能。因此,获取令牌的API需要指定所需的MSI,系统分配的MSI或用户分配的MSI 之一。 HTTP端点执行此操作的方式是,除非您指定clientId,否则它将使用系统分配的MSI。

在任何情况下,您都可以直接访问令牌端点,并提供用户分配的MSI的clientId,如下所示:

public async Task<String> GetToken(string resource, string clientId = null)
{
    var endpoint = System.Environment.GetEnvironmentVariable("MSI_ENDPOINT", EnvironmentVariableTarget.Process);
    var secret = System.Environment.GetEnvironmentVariable("MSI_SECRET", EnvironmentVariableTarget.Process);

    if (string.IsNullOrEmpty(endpoint))
    {
        throw new InvalidOperationException("MSI_ENDPOINT environment variable not set");
    }
    if (string.IsNullOrEmpty(secret))
    {
        throw new InvalidOperationException("MSI_SECRET environment variable not set");
    }

    Uri uri;
    if (clientId == null)
    {
        uri = new Uri($"{endpoint}?resource={resource}&api-version=2017-09-01");
    }
    else
    {
        uri = new Uri($"{endpoint}?resource={resource}&api-version=2017-09-01&clientid={clientId}");
    }

    // get token from MSI
    var tokenRequest = new HttpRequestMessage()
    {
        RequestUri = uri,
        Method = HttpMethod.Get
    };
    tokenRequest.Headers.Add("secret", secret);
    var httpClient = new HttpClient();

    var response = await httpClient.SendAsync(tokenRequest);

    var body = await response.Content.ReadAsStringAsync();
    var result = JObject.Parse(body);

    string token = result["access_token"].ToString();
    return token;

}