Azure - AD - AcquireTokenSilent выдает ошибку failed_to_acquire_token_silently

Мы используем Azure AD для аутентификации и получения обновленного токена доступа каждые 30 минут. Мы вызываем ниже метод, который получает токен безопасности и добавляет его в заголовок запроса.

var userObjectId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
var authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectId));
var credential = new ClientCredential(ConfigurationManager.AppSettings["ida:ClientId"],
ConfigurationManager.AppSettings["ida:ClientSecret"]);

    try
    {
    var authenticationResult = authContext.AcquireTokenSilent(ConfigurationManager.AppSettings["WebAPIBaseAddress"], credential, new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
    //set cookie for azure oauth refresh token - on successful login
    var httpCookie = HttpContext.Current.Response.Cookies["RefreshToken"];
    if (httpCookie != null)
        httpCookie.Value = authenticationResult.RefreshToken;

    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
    }
    catch
    {
    //Get access token using Refresh Token 
    var authenticationResult = authContext.AcquireTokenByRefreshToken(httpCookie.Value, credential, ConfigurationManager.AppSettings["WebAPIBaseAddress"]);
    }

В приведенном выше методе мы использовали метод AcquireTokenSilent, который дает нам токен доступа. Поскольку токен доступа действует только на определенный период времени. По истечении его срока мы вызываем AcquireTokenByRefreshToken, чтобы получить токен обновления.

Приведенный выше код работает хорошо, однако мы случайным образом получаем исключение ниже:

Microsoft.IdentityModel.Clients.ActiveDirectory.AdalSilentTokenAcquisitionException: Failed to acquire token silently. Call method AcquireToken 
   at Microsoft.IdentityModel.Clients.ActiveDirectory.AcquireTokenSilentHandler.SendTokenRequestAsync() 
   at Microsoft.IdentityModel.Clients.ActiveDirectory.AcquireTokenHandlerBase.<RunAsync>d__0.MoveNext()
ErrorCode: failed_to_acquire_token_silently

В чем может быть причина такого непоследовательного поведения? Тот же код работает в нескольких средах (Stage / Dev), но его ошибка генерируется случайным образом в Production.

Пожалуйста, предложите.


person Sushant Sonarghare    schedule 21.01.2016    source источник
comment
Как вы настраивали аутентификацию? Вы видели заголовок этого сообщения stackoverflow.com/questions/34888661/?   -  person Thomas    schedule 21.01.2016
comment
Какую версию вы используете для Microsoft.IdentityModel.Clients.ActiveDirectory?   -  person juvchan    schedule 21.01.2016
comment
@ Томас: Спасибо. Мы выполнили аутентификацию почти так же, как в startup.cs, как указано в ссылке, которой вы поделились.   -  person Sushant Sonarghare    schedule 21.01.2016
comment
@juvchan: конкретная версия Microsoft.IdentityModel.clients.ActiveDirectory, которую мы используем, - 2.16 (2.16.20422.1202)   -  person Sushant Sonarghare    schedule 21.01.2016
comment
Есть ли какая-то конкретная причина придерживаться этой старой версии? Я рекомендую вам перейти на последнюю стабильную версию (2.20.301151232), выпущенную менее недели назад. У меня отлично работает. Я считаю, что он также не содержит критических изменений, нарушающих ваш код. Избегайте использования альфа-версии.   -  person juvchan    schedule 21.01.2016
comment
Спасибо @juvchan. Но как он работает с другими средами, для которых AD настроен в рамках той же подписки с той же базой кода?   -  person Sushant Sonarghare    schedule 21.01.2016
comment
@SushantSonarghare, когда вы упоминаете о несогласованности, вы имеете в виду, что ошибка возникает только в вашей производственной среде и возникает только случайным образом?   -  person juvchan    schedule 21.01.2016
comment
Да @juvchan. Это происходит только на продакшене и слишком случайно.   -  person Sushant Sonarghare    schedule 21.01.2016
comment
Исходя из опыта, это может быть связано с конфигурациями, потому что одна и та же кодовая база отлично работает в других средах ... На данный момент слишком мало информации, чтобы помочь вам определить основную причину   -  person juvchan    schedule 21.01.2016
comment
@SushantSonarghare, когда вы пытаетесь получить токен в автоматическом режиме, он получит токен из TokenCache. Поэтому, если токен больше не действителен, вы получите эту ошибку. что вы можете сделать, так это перехватить AdalException и заставить пользователя повторно аутентифицироваться.   -  person Thomas    schedule 21.01.2016
comment
Примечание: вам никогда не нужно вызывать AcquireTokenByRefreshToken. Если вы вызовете AcquireTokenSilent, ADAL автоматически выберет лучший токен обновления из кеша и прозрачно сохранит новый токен обновления. Позвольте мне подчеркнуть это: предполагая, что вы сохраняете свой кеш, не должно быть никакого сценария, в котором вы должны напрямую манипулировать токеном обновления.   -  person vibronet    schedule 21.01.2016
comment
Спасибо всем за ваши ответы. К сожалению, мы получаем исключение и в других средах.   -  person Sushant Sonarghare    schedule 22.01.2016
comment
Попробуйте включить ведение журнала, чтобы лучше диагностировать. См. stackoverflow.com/q/27364887/18044   -  person MvdD    schedule 23.01.2016
comment
В качестве дополнительного примечания, добавление RefreshToken в качестве файла cookie предоставляет пользовательскому агенту возможность его использовать.   -  person Brent Schmaltz    schedule 28.01.2016


Ответы (2)


Мы смогли решить эту проблему. Вроде бы небольшая ошибка в самом коде. Когда срок действия AccessToken истекает, он генерирует исключение и пытается получить новое, используя AcquireTokenByRefreshToken в блоке catch. Здесь мы не устанавливали вновь полученный токен обновления обратно в файл cookie. Нам также нужно добавить приведенный ниже оператор в блок catch, чтобы он получил токен обновления, который затем можно было бы передать обратно для создания нового токена доступа.

httpCookie.Value = authenticationResult.RefreshToken;
person Sushant Sonarghare    schedule 13.04.2016
comment
Это не то, как следует использовать OAuth! Хранение любого токена в файлах cookie крайне небезопасно (см. security.stackexchange.com/questions/100600/). Кроме того, это приведет вас к странным потокам авторизации. Прочтите еще раз ответ здесь или другой мой ответ (stackoverflow.com/questions/25267831/), чтобы узнать, как обрабатывать обновление жетоны. В основном говоря, что они должны быть прозрачными для вас (приобретение молчания делает это автоматически). - person andrew.fox; 14.04.2016
comment
вы не можете сделать это в методе catch. Здесь проблема в том, что authenticationResult не выбирает данные и в этот момент выдает ошибку. как может иметь значение authenticationResult.RefreshToken? - person Kurkula; 03.03.2017

Прежде всего, перед использованием AcquireTokenSilent вы должны вызвать AcquireTokenByAuthorizationCodeAsync.

var context = new AuthenticationContext(authorityUri);
var credential = new ClientCredential(clientId, clientSecretKey);

await context.AcquireTokenByAuthorizationCodeAsync(authorizationCode, new Uri(redirectUri), credential);

AcquireTokenByAuthorizationCodeAsync сохраняет токен доступа и токен обновления в TokenCache.DefaultShared (для уникального идентификатора пользователя, полученного из процедуры аутентификации).

Если вы это сделаете, срок действия токенов доступа и обновлений истечет. Если это произойдет, вы должны поймать AdalSilentTokenAcquisitionException исключение:

try
{
    // currentUser = new UserIdentifier() for: ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")
    AuthenticationResult authResult = await context.AcquireTokenSilentAsync(resourceUri, credential, currentUser);

    return authResult.AccessToken;
}
catch (AdalSilentTokenAcquisitionException)
{
    return null;
}

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

Но когда бросается AdalSilentTokenAcquisitionException (или это первый звонок). Вы должны вызвать процедуру, которая выполняет полное извлечение кода доступа из oauth API. Какая процедура? Это зависит от типа используемого вами процесса авторизации.

С полной авторизацией owin это может быть:

  • перенаправить на авторитетный uri с {"response_type", "code" }
  • или вызов HttpContext.GetOwinContext().Authentication.Challenge(OpenIdConnectAuthenticationDefaults.AuthenticationType); (возврат null из действия контроллера, поскольку метод Challenge() изменяет ответ HTTP, чтобы принудительно перенаправить на сервер аутентификации). Завершить обработку текущего запроса (с возвращением null). Сервер аутентификации вызовет ваш метод авторизации (событие AuthorizationCodeReceived из уведомлений UseOpenIdConnectAuthentication) с новым кодом авторизации. Позже перенаправьте обратно на исходную страницу, которой нужен токен.

Таким образом, вы можете получить исключение AdalSilentTokenAcquisitionException, потому что срок действия кеша истек, а refreshToken истек. Вам необходимо повторно пройти аутентификацию (это прозрачно, страница входа не требуется).

person andrew.fox    schedule 17.02.2016
comment
Спасибо за подробный ответ и извините за поздний ответ, но мы выполняем предложенные вами шаги. Не могли бы вы подробнее рассказать о разделе получения полного кода вашего ответа? var authContext = new AuthenticationContext (Authority, новый NaiveSessionCache (userObjectId)); authContext.AcquireTokenByAuthorizationCode (код, новый Uri (HttpContext.Current.Request.Url.GetLeftPart (UriPartial.Path)), учетные данные, GraphResourceId); - person Sushant Sonarghare; 21.03.2016
comment
@SushantSonarghare - Какой способ аутентификации вы используете? a) Библиотека Owin .net, как здесь: github.com/Azure-Samples/; или б) библиотека ADAL powerbi. microsoft.com/en-us/documentation/? - person andrew.fox; 29.03.2016