Атрибут авторизации всегда возвращает 401

Я использую поток учетных данных клиента/идентификации приложения (OAuth 2.0), где API может аутентифицировать веб-приложение по его идентификатору приложения. Есть две вещи, которые мне нужны, чтобы убедиться, что аутентификация прошла успешно:

  1. Токен доступа, переданный из веб-приложения для доступа к API, должен быть действительным токеном-носителем (например, не просроченный, действительный формат и т. д.).

  2. Идентификатор приложения из токена доступа должен быть указанным веб-приложением.

Когда я поместил атрибут [authorize] в класс контроллера, он продолжал возвращать 401.

Вот класс startup.cs

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthentication(sharedOptions =>
            {
                sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddAzureAdBearer(options => Configuration.Bind("AzureAd", options));

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseHttpsRedirection();
            app.UseAuthentication();
            app.UseMvc();
        }

Класс AzureAdAuthenticationBuilderExtensions

public static class AzureAdAuthenticationBuilderExtentsions
    {
        public static AuthenticationBuilder AddAzureAdBearer(this AuthenticationBuilder builder)
        => builder.AddAzureAdBearer(_ => { });

        public static AuthenticationBuilder AddAzureAdBearer(this AuthenticationBuilder builder, Action<AzureAdOptions> configureOptions)
        {
            builder.Services.Configure(configureOptions);
            builder.Services.AddSingleton<IConfigureOptions<JwtBearerOptions>, ConfigureAzureOptions>();
            builder.AddJwtBearer();
            return builder;
        }

        private class ConfigureAzureOptions : IConfigureNamedOptions<JwtBearerOptions>
        {
            private readonly AzureAdOptions _azureOptions;

            public ConfigureAzureOptions(IOptions<AzureAdOptions> azureOptions)
            {
                _azureOptions = azureOptions.Value;
            }

            public void Configure(string name, JwtBearerOptions options)
            {
                options.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidAudiences = new string[] {
                        _azureOptions.ClientId,
                        _azureOptions.ClientIdUrl
                    },
                    ValidateAudience = true,
                    ValidateIssuer = true,
                    ValidateIssuerSigningKey = true,
                    ValidateLifetime = true,
                    RequireExpirationTime = true
                };
                options.Audience = _azureOptions.ClientId;
                options.Authority = $"{_azureOptions.Instance}{_azureOptions.TenantId}";
            }

            public void Configure(JwtBearerOptions options)
            {
                Configure(Options.DefaultName, options);
            }
        }
    }

Вот класс AzureAdOptions

 public class AzureAdOptions
    {
        internal static readonly object Settings;

        public string ClientId { get; set; }

        public string ClientIdUrl { get; set; }

        public string ClientSecret { get; set; }

        public string Instance { get; set; }

        public string Domain { get; set; }

        public string TenantId { get; set; }
    }

И класс контроллера

  [Route("api")]
    [ApiController]
public class FindController : ControllerBase
{
    private IConfiguration _configuration;
    HttpClient _client;
    public ContentController( IConfiguration configuration)
    {
        _configuration = configuration;
    }

    private bool ValidateRequest()
    {
        var authHeader = Request.Headers["Authorization"];
        if (StringValues.IsNullOrEmpty(authHeader) || authHeader.Count == 0)
        {
            throw new UnauthorizedAccessException(Messages.AuthHeaderIsRequired);
        }
        var tokenWithBearer = authHeader.Single();
        var token = tokenWithBearer.Substring(7); //remove bearer in the token
        var jwtHandler = new JwtSecurityTokenHandler();
        if (!jwtHandler.CanReadToken(token))
        {
            throw new FormatException("Invalid JWT Token");
        }

        var tokenS = jwtHandler.ReadToken(token) as JwtSecurityToken;
        var appId = tokenS.Audiences.First();
        if (string.IsNullOrEmpty(appId))
        {
            throw new UnauthorizedAccessException(Messages.AppIdIsMissing);
        }
        var registeredAppId = _configuration.GetSection("AzureAd:AuthorizedApplicationIdList")?.Get<List<string>>();
        return (registeredAppId.Contains(appId)) ? true : false;
    }
    [HttpPost("Find")]
    [Produces("application/json")]
    [Authorize]
    public async Task<IActionResult> Find()
    {
        try
        {
            if (!ValidateRequest())
            {
                return Unauthorized();
            }
         return new ObjectResult("hello world!");
        }
        catch (InvalidOperationException)
        {
            return null;
        }
    }
}

Кто-нибудь знает, почему он продолжает возвращать ошибку 401? Одна вещь, которую я хотел бы упомянуть, заключается в том, что между тем, как я начинаю вызывать API, и до тех пор, пока он не вернет ошибку 401, точки останова внутри класса контроллера никогда не срабатывали...

Вы можете опубликовать соответствующий код для получения маркера доступа с помощью AAD версии 1.0 или версии 2.0?

Nan Yu 20.03.2019 04:15

да, я понял, что то, как я сгенерировал токен доступа, неверно. Я нашел учебник здесь, но в настоящее время у меня проблемы с созданием токена: stackoverflow.com/questions/55252940/…

superninja 20.03.2019 04:19

@NanYu Токен доступа теперь генерируется правильно. Спасибо за вашу помощь. Но все же API возвращает 401 с атрибутом [Authorize] в классе контроллера. В классе controller и классе AzureAdAuthenticationBuilderExtentsions не было остановок. Есть идеи, почему?

superninja 20.03.2019 07:37

Каков ваш _azureOptions.ClientId? Идентификатор клиента приложения API?

Nan Yu 20.03.2019 08:07
_azureOptions.ClientId — это идентификатор приложения из моего целевого приложения.
superninja 20.03.2019 08:10

Позвольте мне подтвердить, что вы указали идентификатор приложения API / URL-адрес идентификатора приложения в качестве допустимой аудитории, а не идентификатор приложения вашего клиентского приложения?

Nan Yu 20.03.2019 08:15

Давайте продолжить обсуждение в чате.

superninja 20.03.2019 08:18
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
7
640
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я бы сделал 2 пункта для проверки:

1. Проверьте, соответствует ли идентификатор приложения AuthorizedApplicationIdList в вашем коде.

Я думаю, что способ, которым вы описали условия для проверки на словах, хорош, но есть проблема с тем, как вы реализовали второе условие в коде.

  1. The app id from the access token has to be the specified web app

При реализации этого условия кажется, что вы устанавливаете appId как значение из aud, т.е. требования аудитории в токене. Это неверно, потому что аудиторией всегда будет ваш собственный API, для которого предназначен этот токен.

Вместо этого вы хотите проверить значение утверждения appid в токене, которое будет идентификатором приложения для клиента, получившего этот токен. Это должен быть идентификатор приложения внешнего веб-приложения, который вы хотите сравнить со своим списком авторизованных приложений.

Взгляните на Справочник Microsoft Docs по маркерам доступа

Кроме того, вы можете легко убедиться в этом, расшифровав токен с помощью https://jwt.ms.

Соответствующий код из вашего поста, где я вижу проблему:

    var appId = tokenS.Audiences.First();
    if (string.IsNullOrEmpty(appId))
    {
        throw new UnauthorizedAccessException(Messages.AppIdIsMissing);
    }
    var registeredAppId = _configuration.GetSection("AzureAd:AuthorizedApplicationIdList")?.Get<List<string>>();
    return (registeredAppId.Contains(appId)) ? true : false;

2. Общий журнал/отладка

Кроме того, в качестве примечания, вы, вероятно, можете отлаживать или помещать операторы журнала/трассировки в свой код API, чтобы точно узнать, где он дает сбой в вашем коде... или если он неожиданно дает сбой где-то еще до того, как вызывается ваша пользовательская логика. Возможно, пока выполняются некоторые из первоначальных проверок.

хм, на самом деле токены поступают из веб-приложения. Поэтому аудиториями будет идентификатор приложения веб-приложения, а не идентификатор приложения моего API.

superninja 20.03.2019 01:54

@WWpana Я обновил ответ, включив в него ссылку на токены доступа из Microsoft Docs, чтобы показать описание обоих утверждений aud и appid.. Я бы все же сказал, что аудиторией будет идентификатор URI идентификатора приложения для вашего веб-API.. и appid будет идентификатор приложения для вашего внешнего веб-приложения, которое получило токен. Хороший способ проверить это - декодировать токен в jwt.ms Насколько я понимаю, веб-приложение получает токен доступа ДЛЯ веб-API.. и поскольку токен доступа предназначен для веб-API, вот что говорит aud претензия или целевая аудитория

Rohit Saigal 20.03.2019 02:02

Спасибо, это имеет смысл. Что касается вашего второго пункта, я пытался отладить, но он никогда не попадал ни в одну из моих точек останова в классе контроллера или где-либо в решении. Знаете ли вы, что может вызвать эту проблему?

superninja 20.03.2019 02:13

@WWpana исходный код конфигурации, в котором у вас есть TokenValidationParameters, будет еще одной областью для проверки ... не уверен, есть ли у вас там точки останова / операторы журнала ..

Rohit Saigal 20.03.2019 02:36
appid больше не существует в токене OAuth2.0. Я предполагаю, что oid будет альтернативой?
superninja 20.03.2019 03:00

Претензия @WWpana azp была бы альтернативой. Возможно, вы используете конечную точку версии 2.0. Для токенов из конечной точки v1 appid претензия предоставит идентификатор приложения, а в случае конечной точки v2 azp сделает то же самое. oid — это другое утверждение, представляющее идентификатор объекта.

Rohit Saigal 20.03.2019 03:13
Ответ принят как подходящий

Если ресурс App ID URI приложения API при получении токена доступа для доступа к приложению API. В приложении API разрешенная аудитория также должна включать App ID URI приложения API.

Другие вопросы по теме