Где отфильтровать билет утверждения Identity 2.0 в приложении WebAPI?

Приложения ASP.NET, использующие OWIN, допускают использование нескольких источников удостоверений (Facebook, Google и т. д.). Большая часть информации, предоставляемой этими источниками, не имеет отношения к моему приложению, потенциально даже большой, и я не хочу, чтобы она хранилась в моих файлах cookie на протяжении всего сеанса. Мое приложение в основном представляет собой WebAPI, но я подозреваю, что этот вопрос в равной степени относится и к MVC, и к WebForms.

На данный момент все, что мне нужно, это целочисленный идентификатор учетной записи. Где и когда мне следует восстановить личность после внешней аутентификации?

Например, вот один из способов фильтрации утверждений:

public ReplaceExistingClaims(ClaimsIdentity identity) {
{
    Claim customClaim = GetCustomClaimFromDbForIdentity(identity);
    foreach (Claim claim in ClaimsIdentity.Claims) ClaimsIdentity.RemoveClaim(claim);
    ClaimsIdentity.AddClaim(customClaim);
}

Ниже приведены два разных места, где я мог бы вводить эти изменения утверждений:

var facebookAuthenticationOptions = new FacebookAuthenticationOptions
{
    Provider = new FacebookAuthenticationProvider
    {
        OnAuthenticated = context =>
        {
            ReplaceExistingClaims(context.Identity);
            return Task.FromResult(0);
        }
    }
};

Выше я знаю, что могу подключить отдельного провайдера из Startup, ЕСЛИ он предоставляет событие Authenticated. У меня есть две концептуальные проблемы с этим. Во-первых, мне нужно написать и подключить свой код отдельно для каждого провайдера, которого я подключаю. Во-вторых, провайдеры не обязаны предоставлять это событие. Оба они заставляют меня чувствовать, что для моего кода должна быть другая предполагаемая точка вставки.

public ActionResult ExternalLoginCallback(string returnUrl)
{
    ReplaceExistingClaims((ClaimsIdentity)User.Identity);
    new RedirectResult(returnUrl);
}

Выше я знаю, что могу поместить код в ExternalLoginCallback. Но это происходит слишком поздно по двум причинам. Первый: Пользователю уже выдан тикет, который я считаю недействительным, но [Authorized] по умолчанию считает действительным, потому что он подписан мной, и теперь они делают запросы на мой сайт с его помощью. Здесь могут быть даже условия гонки. Второе: нет никакой гарантии, что браузер посетит это перенаправление, и я бы предпочел с точки зрения дизайна, если в этом нет необходимости, например. чтобы упростить мой клиентский код WebAPI.

Насколько мне известно, лучшее решение будет соответствовать следующим требованиям:

  1. один и тот же код применяется ко всем провайдерам
  2. клиент получает мой пользовательский билет с моего сервера (например, без претензий на изображение)
  3. клиент никогда не получает другой формат билета с моего сервера
  4. процесс аутентификации требует минимально возможных двусторонних HTTP-трафиков
  5. обновление маркера и другие основные функции идентификации по-прежнему доступны
  6. как только пользователь становится [Authorize]d, дальнейшая трансформация учетной записи не требуется.
  7. доступ к базе данных/репозиторию возможен во время создания заявки

Некоторые страницы, которые я изучаю, для собственных заметок:


person shannon    schedule 30.03.2015    source источник
comment
Не могли бы вы уточнить, что такое поток?   -  person deeptowncitizen    schedule 07.04.2015
comment
Конечно. Попытка доступа к приложению -> Аутентификация через Facebook/Google -> Назначение пользовательского билета -> Авторизация. Предположительно за этим последует: Попытка дополнительного доступа -> Представление пользовательского билета -> Авторизация. Пользовательский билет будет содержать минимум, необходимый для этого, в то время как согласование аутентификации может привести к другим дополнительным данным, таким как фотография профиля.   -  person shannon    schedule 07.04.2015
comment
В основном вам нужно отправить запрос на сервер для входа в систему с любым из провайдеров? Этот запрос должен быть асинхронным? И после того, как вы войдете в систему, клиент должен получить уведомление и соответствующая информация должна быть загружена?   -  person deeptowncitizen    schedule 08.04.2015
comment
@deeptowncitizen: вы спрашиваете о самом процессе аутентификации, и у меня нет проблем с ответом, но я не думаю, что это действительно имеет отношение к моему вопросу. Я доволен процессом аутентификации OAuth2, поскольку он реализован в настоящее время. Мой вопрос в том, что является правильным местом/способом предоставить веб-браузеру пользователя мой личный идентификационный билет после этого процесса аутентификации? По умолчанию Identity 2.0 создает файл cookie токена аутентификации, размер которого примерно в 100 раз превышает размер моего предыдущего токена, и в настоящее время я не могу его контролировать.   -  person shannon    schedule 08.04.2015


Ответы (2)


Вы должны реализовать DelegationHandler и поместить в него все ваши процедуры аутентификации.

Зарегистрируйтесь при запуске приложения (использование DI включено):

private static void RegisterHandlers(HttpConfiguration config)
{
    var authHandler = new MyFacebookAuthHandler();
    config.MessageHandlers.Add(authHandler);
}

А это пример реализации:

public class MyFacebookAuthHandler : DelegationHandler
{
    public override sealed Task<HttpResponseMessage> OnSendAsync(HttpRequestMessage request,
                                                                 CancellationToken cancellationToken)
    {
        try
        {
            // Process credentials
            // Probably you have to save some auth information to HttpContext.Current
            // Or throw NotAuthorizedException
        }
        catch(NotAuthorizedException ex)
        {
            return request.CreateErrorResponse(HttpStatusCode.Unauthorized, ex).ToCompletedTask();
        }
        catch (Exception ex)
        {
            return request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex).ToCompletedTask();
        }

        return base.OnSendAsync(request, cancellationToken);
    }
}
person deeptowncitizen    schedule 07.04.2015
comment
Спасибо за ответ. Я не уверен, что ты здесь делаешь. Я предполагаю, что под DelegationHandler вы на самом деле подразумеваете DelegatingHandler, а OnSendAsync это SendAsync? Если да, то я не вижу, как это помогает. Я хочу изменить претензию, предоставленную клиентам. Это должно произойти один раз, в момент завершения аутентификации. Им по-прежнему необходимо сначала пройти аутентификацию, и этот шаг обычно требует перенаправления клиента к внешнему поставщику. Следовательно, учетные данные не могут быть обработаны в MessageHandler. Я неправильно тебя понимаю? - person shannon; 07.04.2015
comment
После дополнительных исследований я вижу, что DelegatingHandler действительно выполняет каждый запрос, подобно стандартному модулю OWIN, который я уже использую для получения внешней аутентификации. Прошу прощения, если я недостаточно ясно выразился в своем вопросе. - person shannon; 11.04.2015

Класс ClaimsAuthenticationManager специально для этого.

https://msdn.microsoft.com/en-us/library/system.security.claims.claimsauthenticationmanager(v=vs.110).aspx

Пример кода из этой ссылки:

class SimpleClaimsAuthenticatonManager : ClaimsAuthenticationManager
{
    public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
    {
        if (incomingPrincipal != null && incomingPrincipal.Identity.IsAuthenticated == true)
        {
            ((ClaimsIdentity)incomingPrincipal.Identity).AddClaim(new Claim(ClaimTypes.Role, "User"));
        }
        return incomingPrincipal; 
    }
}
person shannon    schedule 10.04.2015
comment
p.s. Я рад, что наткнулся на это, потому что собирался реализовать это как отдельную прокладку OWIN. - person shannon; 10.04.2015
comment
Кроме того, в этом документе RP означает доверяющая сторона. Эх, какого-то технического писателя нужно уволить. - person shannon; 10.04.2015