User.Identity колеблется между ClaimsIdentity и WindowsIdentity - PullRequest
0 голосов
/ 08 мая 2018

У меня есть сайт MVC, который позволяет входить в систему с помощью входа с помощью форм и проверки подлинности Windows. Я использую пользовательский MembershipProvider, который аутентифицировал пользователей с помощью Active Directory, класса System.Web.Helpers AntiForgery для защиты от CSRF и промежуточного программного обеспечения аутентификации файлов cookie Owin.

Во время входа в систему, когда пользователь прошел аутентификацию в Active Directory, я делаю следующее:

IAuthenticationManager authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.SignOut(StringConstants.ApplicationCookie);
var identity = new ClaimsIdentity(StringConstants.ApplicationCookie,
    ClaimsIdentity.DefaultNameClaimType,
    ClaimsIdentity.DefaultRoleClaimType);
if(HttpContext.Current.User.Identity is WindowsIdentity)
{
    identity.AddClaims(((WindowsIdentity)HttpContext.Current.User.Identity).Claims);
}
else
{
    identity.AddClaim(new Claim(ClaimTypes.Name, userData.Name));
}
identity.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "Active Directory"));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userData.userGuid));
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = false }, identity);

Моя функция SignOut выглядит следующим образом:

IAuthenticationManager authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.SignOut(StringConstants.ApplicationCookie);

Вход в систему осуществляется через запрос jQuery.ajax. В случае успеха Window.location обновляется до главной страницы сайта.

Вход в систему с помощью форм и IntegratedWindowsAuthentication (IWA) работает, но я столкнулся с проблемой при входе в систему с IWA. Вот что происходит:

  1. Пользователь выбирает IWA на странице входа и нажимает кнопку отправки. Это отправляется обычному действию входа в систему через ajax-запрос.
  2. Сайт получает запрос, видит опцию «использовать IWA» и перенаправляет на соответствующее действие. 302 ответ отправлен.
  3. Браузер автоматически обрабатывает ответ 302 и вызывает цель перенаправления.
  4. Фильтр видит, что запрос направляется к действию входа в IWA и что User.Identity.IsAuthenticated == false. 401 ответ отправлен.
  5. Браузер автоматически обрабатывает ответ 401. Если пользователь еще не прошел аутентификацию с использованием IWA в браузере, он получает всплывающее окно для этого (поведение браузера по умолчанию). После получения учетных данных браузер выполняет тот же запрос с учетными данными пользователя.
  6. Сайт получает аутентифицированный запрос и олицетворяет пользователя для проверки Active Directory. Если пользователь проходит аутентификацию, мы завершаем вход в систему, используя приведенный выше код.
  7. Пользователь перенаправляется на главную страницу сайта.
  8. Сайт получает запрос на загрузку главной страницы. Здесь все иногда идет наперекосяк .
    User.Identity на данный момент имеет тип WindowsIdentity с AuthenticationType, установленным на Negotiate, и NOT , как я и ожидал, ClaimsIdentity, созданным в методе SignIn выше.
    Сайт готовит главную страницу для пользователя, вызывая @AntiForgery.GetHtml() в представлении. Это сделано для создания нового токена AntiForgery с зарегистрированными данными пользователя. Токен создается с WindowsIdentity
  9. При загрузке главной страницы на сервер поступают запросы ajax с ClaimsIdentity! Поэтому первый POST запрос на поступление неизбежно вызывает AntiForgeryException, когда отправляемый им токен подделки «для другого пользователя».

Обновление страницы приводит к загрузке главной страницы с ClaimsIdentity и позволяет POST запросам функционировать.

Второе, связанное, проблема : В любой момент после обновления, когда все предположительно работает должным образом, запрос POST может поступить с WindowsIdentity, а не с ClaimsIdentity, снова выбрасывая AntiForgeryException.

  • Это , а не любой конкретный почтовый запрос,
  • это , а не по истечении определенного промежутка времени (может быть первый / второй запрос, может быть сотым),
  • это , а не , обязательно первый раз, когда конкретный почтовый запрос вызывался во время этого сеанса.

Мне кажется, что я либо что-то упустил из-за User.Identity или что я что-то не так сделал в процессе входа в систему ... Есть идеи?

Примечание : настройка AntiForgeryConfig.SuppressIdentityHeuristicChecks = true; позволяет действию AntiForgery.Validate успешно выполняться при получении WindowsIdentity или ClaimsIdentity, но, как указано в MSDN:

Будьте осторожны при установке этого значения. Использование его неправильно может открыть уязвимости в приложении.

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

1 Ответ

0 голосов
/ 14 мая 2018

Оказывается, проблема заключалась в том, что ClaimsPrincipal поддерживал несколько идентификаторов. Если вы находитесь в ситуации, когда у вас есть несколько идентификаторов, он выбирает один самостоятельно. Я не знаю, что определяет порядок тождеств в IEnumerable, но каким бы он ни был, он, очевидно, обязательно приводит к постоянному порядку в течение жизненного цикла сеанса пользователя.

Как уже упоминалось в разделе «Проблемы» gp asp.net/Security, Проверка подлинности NTLM и cookie # 1467 :

Идентификационные данные содержат как идентификатор Windows, так и идентификационный файл cookie.

и

Похоже, что с ClaimsPrincipals вы можете установить static Func<IEnumerable<ClaimsIdentity>, ClaimsIdentity> с именем PrimaryIdentitySelector, который можно использовать для выбора основного идентификатора для работы.

Для этого создайте статический метод с подписью:

static ClaimsIdentity MyPrimaryIdentitySelectorFunc(IEnumerable<ClaimsIdentity> identities)

Этот метод будет использоваться для просмотра списка ClaimsIdentity s и выбора того, который вы предпочитаете.
Затем в вашем Global.asax.cs установите этот метод как PrimaryIdentitySelector, например так:

System.Security.Claims.ClaimsPrincipal.PrimaryIdentitySelector = MyPrimaryIdentitySelectorFunc;

Мой PrimaryIdentitySelector метод в итоге выглядел так:

public static ClaimsIdentity PrimaryIdentitySelector(IEnumerable<ClaimsIdentity> identities)
{
    //check for null (the default PIS also does this)
    if (identities == null) throw new ArgumentNullException(nameof(identities));

    //if there is only one, there is no need to check further
    if (identities.Count() == 1) return identities.First();

    //Prefer my cookie identity. I can recognize it by the IdentityProvider
    //claim. This doesn't need to be a unique value, simply one that I know
    //belongs to the cookie identity I created. AntiForgery will use this
    //identity in the anti-CSRF check.
    var primaryIdentity = identities.FirstOrDefault(identity => {
        return identity.Claims.FirstOrDefault(c => {
            return c.Type.Equals(StringConstants.ClaimTypes_IdentityProvider, StringComparison.Ordinal) &&
                   c.Value == StringConstants.Claim_IdentityProvider;
        }) != null;
    });

    //if none found, default to the first identity
    if (primaryIdentity == null) return identities.First();

    return primaryIdentity;
}

[Изменить]
Теперь этого оказалось недостаточно, так как PrimaryIdentitySelector, похоже, не работает, когда в списке Identities есть только один Identity. Это вызвало проблемы на странице входа в систему, когда браузер иногда передавал идентификатор WindowsIdentity при загрузке страницы, но не передавал его по запросу входа в систему {вздохнул вздох}. Чтобы решить это я закончил тем, что создал ClaimsIdentity для страницы входа в систему, затем вручную переписал принципала потока, как описано в этом SO вопросе .

Это создает проблему с проверкой подлинности Windows, так как OnAuthenticate не отправит 401 для запроса идентификатора Windows. Чтобы решить это , вы должны выйти из учетной записи. Если вход в систему не удается, не забудьте воссоздать пользователя входа в систему. (Вам также может понадобиться воссоздать токен CSRF)

...