Я пытаюсь заставить мой веб-API .Net (приложение API Azure с использованием OWIN) принять токен носителя OAuth для предоставления client_credentials, но я продолжаю получать 401 Unauthorized.
Также кажется, что все образцы Microsoft устарели (не соответствуют последним пакетам nuget для Owin).
JwtFormat выполняет IIssuerSecurityTokenProvider, но его больше не существует — вместо этого JwtFormat ожидает IIssuerSecurityKeyProvider, но я не могу понять, как его использовать.
Сервер Azure OAuth работает
Я зарегистрировал два приложения (API и клиент) в Azure Active Directory. Так как это всего лишь краткая демонстрация, я дам вам все идентификаторы и секреты ;)
Я могу получить токен из Azure AD, полный запрос см. в https://reqbin.com/817shtc2, пока все хорошо.
API
ClientId: 44cf7574-88a2-42d6-9497-bff43cc8dc09
Endpoint: https://apim-demo-mglentoft.azure-api.net/api/Values (GET)
Клиент
ClientId: 5f7ee334-b8db-46d3-972f-09f52e186d1d
Secret: ggKp94]HZHWZ.c*5wUC?ToSVfknyqLB3
Я следовал образцу в https://github.com/azureadquickstarts/appmodelv2-nativeclient-dotnet, но он ссылается на Microsoft.Owin.Security.Jwt.IIssuerSecurityTokenProvider, которого нет в nuget v4.0.1.0.
Я попытался просто закомментировать второй параметр для JwtFormat, но это не сработало. Есть идеи, как заставить это работать с помощью Microsoft.Owin.Security.Jwt.IIssuerSecurityKeyProvider?
Ниже весь файл startup.cs
Я могу заставить это работать с помощью .Net Core, но по разным причинам мне приходится придерживаться .Net Framework 4.7.2.
using System;
using System.Configuration;
using System.Threading.Tasks;
using System.Web.Http;
using DemoAPI.Middleware;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin;
using Microsoft.Owin.Cors;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Jwt;
using Microsoft.Owin.Security.OAuth;
using Owin;
[assembly: OwinStartup(typeof(DemoAPI.App_Start.Startup))]
namespace DemoAPI.App_Start
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
ConfigureAuth(app);
app.Use(typeof(CorrelationHandlerMiddleware));
app.UseCors(CorsOptions.AllowAll);
WebApiConfig.Register(config);
app.UseWebApi(config);
}
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
public void ConfigureAuth(IAppBuilder app)
{
// NOTE: The usual WindowsAzureActiveDirectoryBearerAuthentication middleware uses a
// metadata endpoint which is not supported by the v2.0 endpoint. Instead, this
// OpenIdConnectSecurityTokenProvider implementation can be used to fetch & use the OpenIdConnect
// metadata document - which for the v2 endpoint is https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(
new TokenValidationParameters
{
// Check if the audience is intended to be this application
ValidAudiences = new[] { clientId, $"api://{clientId}" },`enter code here`
// Change below to 'true' if you want this Web API to accept tokens issued to one Azure AD tenant only (single-tenant)
// Note that this is a simplification for the quickstart here. You should validate the issuer. For details,
// see https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore
ValidateIssuer = false,
}//,
//new OpenIdConnectSecurityKeyProvider("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration")
//the OpenIdConnectSecurityKeyProvider implements IIssuerSecurityTokenProvider, which is not part of Microsoft.Owin.Security.Jwt 4.0
),
});
}
}
}
Актуальная ошибка при использовании Microsoft.Owin.diagnostics
Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationMiddleware Error: 0 : Authentication failed Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException: IDX10501: Signature validation failed. Unable to match key: kid: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. Exceptions caught: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. token: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'. at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)


попробуйте переименовать «токен» в «ключ», и вы можете быть в порядке.
Итак, вместо
IssuerSecurityTokenProviders = new IIssuedSecurityTokenProvider[]
{
new symmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
}
У вас должно быть что-то вроде
IssuerSecurityKeyProviders= new IIssuerSecurityKeyProvider[]
{
new SymmetricKeyIssuerSecurityKeyProvider(issuer, audienceSecret)
}
Для получения дополнительной информации: Тема выпуска или Актуальный репозиторий Github.
Надеюсь, что это поможет вам...
Я продолжу и отвечу на свой вопрос здесь, так как я заставил его работать, немного изменив образец в https://github.com/azureadquickstarts/appmodelv2-nativeclient-dotnet.
Возможно, ответ Стефа тоже работает, и я просто сделал что-то не так, но вы направили меня в правильном направлении :)
в Startup.cs:
Я перешел с реализации IIssuerSecurityTokenProvider на IIssuerSecurityKeyProvider (спасибо Стеф)
string issuerEndpoint = @"https://sts.windows.net/919e9a01-27bf-4106-9d36-48528249d0ce/";
string metadaEndpoint = $"{issuerEndpoint}v2.0/.well-known/openid-configuration";
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(
new TokenValidationParameters
{
// Check if the audience is intended to be this application
ValidAudiences = new[] { clientId, $"api://{clientId}" },
// Change below to 'true' if you want this Web API to accept tokens issued to one Azure AD tenant only (single-tenant)
// Note that this is a simplification for the quickstart here. You should validate the issuer. For details,
// see https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore
ValidateIssuer = false,
},
new OpenIdConnectSecurityKeyProvider("https://sts.windows.net/919e9a01-27bf-4106-9d36-48528249d0ce/v2.0/.well-known/openid-configuration")
),
});
OpenIDConnectSecurityKeyProvider.cs
Реализация, основанная на образце из https://github.com/azureadquickstarts/appmodelv2-nativeclient-dotnet, изменена для извлечения ключей вместо токенов.
using System.Collections.Generic;
using System.Threading;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Security.Jwt;
namespace DemoAPI.App_Start
{
//This class is necessary because the OAuthBearer Middleware does not leverage
// the OpenID Connect metadata endpoint exposed by the STS by default.
public class OpenIdConnectSecurityKeyProvider : IIssuerSecurityKeyProvider
{
public ConfigurationManager<OpenIdConnectConfiguration> ConfigManager;
private string _issuer;
private IEnumerable<SecurityKey> _keys;
private readonly string _metadataEndpoint;
private readonly ReaderWriterLockSlim _synclock = new ReaderWriterLockSlim();
public OpenIdConnectSecurityKeyProvider(string metadataEndpoint)
{
_metadataEndpoint = metadataEndpoint;
ConfigManager = new ConfigurationManager<OpenIdConnectConfiguration>(metadataEndpoint, new OpenIdConnectConfigurationRetriever());
RetrieveMetadata();
}
/// <summary>
/// Gets the issuer the credentials are for.
/// </summary>
/// <value>
/// The issuer the credentials are for.
/// </value>
public string Issuer
{
get
{
RetrieveMetadata();
_synclock.EnterReadLock();
try
{
return _issuer;
}
finally
{
_synclock.ExitReadLock();
}
}
}
public IEnumerable<SecurityKey> SecurityKeys
{
get
{
RetrieveMetadata();
_synclock.EnterReadLock();
try
{
return _keys;
}
finally
{
_synclock.ExitReadLock();
}
}
}
private void RetrieveMetadata()
{
_synclock.EnterWriteLock();
try
{
OpenIdConnectConfiguration config = ConfigManager.GetConfigurationAsync().Result;
_issuer = config.Issuer;
_keys = config.SigningKeys;
}
finally
{
_synclock.ExitWriteLock();
}
}
}
}
Спасибо за ответ. Это звучит немного странно для меня, поскольку алгоритм RS256 (асимметричный), поэтому передача SymmetricKeyIssuerSecurityKeyProvider не имеет смысла...? Хотя я пробовал (с клиентским ключом), но он не смог найти ключ с идентификатором xxxxxxx.