Почему кеш-ключ доступа считается плохой в oauth2?

Я следую этой статье для отзыва доступа пользователя:

http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/

Теперь рассмотрим после проверки пользователя, что я выпустил accesstoken с 30-минутной продолжительностью жизни, как показано в предыдущей статье, и с токеном обновления как 1 день, но что, если администратор удалит этого пользователя за 10 минут с 20 минутами, оставшимися сейчас, теперь в этом случае Мне нужно отменить доступ к этому пользователю.

Для этого мне нужно удалить эту запись пользователя из обновленной таблицы токенов, чтобы запретить дальнейший запрос доступа к токену, но поскольку время истечения срока действия для доступа еще не достигло 20 минут, чтобы пользователь мог получить доступ к защищенному ресурсу, который является полностью неправильным.

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

Но в приведенных ниже ответах говорится, что это не так, как был разработан oauth2:

Отменить токен доступа OAuthBearerAuthentication

OAuth2 - ненужная сложность с токеном обновления

Итак, мой вопрос:

1) Почему кеширование маркера доступа не считается лучше, чем обновление механизма токена, а также плохой подход?

Мой второй вопрос основан на этом ниже ответе, указанном @Hans Z., в котором он говорит, что:

Это обязательно будет связано с сервером ресурсов (RS), который консультирует Сервер авторизации (AS), который представляет собой огромные накладные расходы.

2) В случае отзыва доступа для пользователя, почему RS будет обращаться к AS, поскольку AS предназначена только для аутентификации пользователя и создания токена доступа в соответствии с этим Article?

3) В статье есть только 2 проекта:

  • Authentication.api. Аутентификация пользователя и создание токена доступа.
  • Сервер ресурсов - проверка accesstoken с помощью атрибута [Authorize]

    В приведенном выше случае, который является сервером авторизации?

Обновление: Я решил использовать токен обновления, чтобы аннулировать доступ пользователя в случае удаления пользователя, а также при выходе пользователя из системы я обновляю токен из обновленной таблицы токенов из-за вашего требования, что мы хотим выйти из системы немедленно, как только пользователь нажмет на выход.

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

Итак, это еще одна проблема, с которой я сталкиваюсь.

Ответ 1

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

Ток доступа

OAuth2 был разработан, чтобы иметь возможность обрабатывать высокую нагрузку, и это требует некоторых компромиссов. В частности, это причина, по которой OAuth2 явно разделяет роли "сервер ресурсов" и "сервер авторизации", с одной стороны, и "токен доступа" и "токен обновления", с другой стороны. Если для каждого запроса вы должны проверить авторизацию пользователя, это означает, что ваш сервер авторизации должен иметь возможность обрабатывать все запросы в вашей системе. Для систем с высокой нагрузкой это невозможно.

OAuth2 позволяет сделать следующее компромисс между производительностью и безопасностью: Authorization Server генерирует токен доступа, который может быть проверен сервером ресурсов без доступа к серверу авторизации (либо вообще, либо по крайней мере не более одного раза для жизни сервера авторизации). Это эффективно кэширование информации авторизации. Таким образом, вы можете резко снизить нагрузку на свой сервер авторизации. Недостатком является то же самое, что и при кэшировании: информация о авторизации может быть остановлена. Изменяя время жизни токена доступа, вы можете настроить производительность и баланс безопасности.

Этот подход также может помочь, если вы выполняете архитектуру микросервисов, где каждая служба имеет собственное хранилище и не имеет доступа друг к другу.

Тем не менее, если у вас мало нагрузки, и у вас есть только один сервер ресурсов, а не множество различных сервисов, реализованных с использованием разных технологий, нет ничего, что бы запрещало вам выполнять полномасштабную проверку на каждый запрос. То есть да, вы можете хранить токен доступа в БД, проверять его при каждом доступе к серверу ресурсов и удалять все токены доступа, когда пользователь удаляется, и т.д. Но, как заметил @Evk, если это ваш сценарий - OAuth2 - это перерегулирование для вас.

Большой список ролей

AFAIU OAuth2 не предоставляет явной функции для ролей пользователей. Существует функция "Scopes", которая также может использоваться для ролей и ее типичной реализации, она создаст слишком длинную строку для 250 ролей. Тем не менее OAuth2 явно не указывает какой-либо конкретный формат для токена доступа, поэтому вы можете создать собственный токен, который будет содержать информацию о ролях в виде битовой маски. Используя кодировку base-64, вы можете получить 6 ролей в один символ (64 = 2 ^ 6). Таким образом, 250-300 ролей будут управляемыми 40-50 символами.

JWT

Так как вам, вероятно, понадобится какой-то пользовательский маркер, вы можете быть заинтересованы в JSON Web Tokens, а также JWT. Короче говоря, JWT позволяет указать пользовательскую дополнительную полезную нагрузку (частные заявки) и поместить туда свою битмассу.

Фактически вы можете использовать JWT самостоятельно без целых вещей OAuth2, если вам действительно не нужны какие-либо дополнительные функции OAuth2 (такие как области видимости). Хотя JWT-токены должны быть подтверждены только содержимым theis, вы все равно можете хранить их в своей локальной базе данных и выполнять дополнительную проверку в отношении БД (как вы делали это с токеном обновления доступа).


Обновление 1 декабря 2017 года

Если вы хотите использовать OWIN OAuth-инфраструктуру, вы можете настроить формат токена, предоставляя пользовательский форматтер через AccessTokenFormat в OAuthBearerAuthenticationOptions и OAuthAuthorizationServerOptions. Вы также можете переопределить RefreshTokenFormat.

Вот эскиз, который показывает, как вы можете "сжимать" роли в одной битовой маске:

  • Определите перечисление CustomRoles, в котором перечислены все ваши роли.
[Flags]
public enum CustomRoles
{
    Role1,
    Role2,
    Role3,

    MaxRole // fake, for convenience
}
  1. Создайте методы EncodeRoles и DecodeRoles для преобразования между форматом IEnumerable<string> для ролей и бит-битовую кодировку base64 на основе CustomRoles, определенных выше, например:
    public static string EncodeRoles(IEnumerable<string> roles)
    {
        byte[] bitMask = new byte[(int)CustomRoles.MaxRole];
        foreach (var role in roles)
        {
            CustomRoles roleIndex = (CustomRoles)Enum.Parse(typeof(CustomRoles), role);
            var byteIndex = ((int)roleIndex) / 8;
            var bitIndex = ((int)roleIndex) % 8;
            bitMask[byteIndex] |= (byte)(1 << bitIndex);
        }
        return Convert.ToBase64String(bitMask);
    }

    public static IEnumerable<string> DecodeRoles(string encoded)
    {
        byte[] bitMask = Convert.FromBase64String(encoded);

        var values = Enum.GetValues(typeof(CustomRoles)).Cast<CustomRoles>().Where(r => r != CustomRoles.MaxRole);

        var roles = new List<string>();
        foreach (var roleIndex in values)
        {
            var byteIndex = ((int)roleIndex) / 8;
            var bitIndex = ((int)roleIndex) % 8;
            if ((byteIndex < bitMask.Length) && (0 != (bitMask[byteIndex] & (1 << bitIndex))))
            {
                roles.Add(Enum.GetName(typeof(CustomRoles), roleIndex));
            }
        }

        return roles;
    }
  1. Используйте эти методы в пользовательской реализации SecureDataFormat<AuthenticationTicket>. Для простоты в этом эскизе я делегирую большую часть работы стандартным компонентам OWIN и просто реализую свой CustomTicketSerializer, который создает другой AuthenticationTicket и использует стандартный DataSerializers.Ticket. Это, очевидно, не самый эффективный способ, но он показывает, что вы можете сделать:
public class CustomTicketSerializer : IDataSerializer<AuthenticationTicket>
{

    public const string RoleBitMaskType = "RoleBitMask";
    private readonly IDataSerializer<AuthenticationTicket> _standardSerializers = DataSerializers.Ticket;

    public static SecureDataFormat<AuthenticationTicket> CreateCustomTicketFormat(IAppBuilder app)
    {
        var tokenProtector = app.CreateDataProtector(typeof(OAuthAuthorizationServerMiddleware).Namespace, "Access_Token", "v1");
        var customTokenFormat = new SecureDataFormat<AuthenticationTicket>(new CustomTicketSerializer(), tokenProtector, TextEncodings.Base64Url);
        return customTokenFormat;
    }

    public byte[] Serialize(AuthenticationTicket ticket)
    {
        var identity = ticket.Identity;
        var otherClaims = identity.Claims.Where(c => c.Type != identity.RoleClaimType);
        var roleClaims = identity.Claims.Where(c => c.Type == identity.RoleClaimType);
        var encodedRoleClaim = new Claim(RoleBitMaskType, EncodeRoles(roleClaims.Select(rc => rc.Value)));
        var modifiedClaims = otherClaims.Concat(new Claim[] { encodedRoleClaim });
        ClaimsIdentity modifiedIdentity = new ClaimsIdentity(modifiedClaims, identity.AuthenticationType, identity.NameClaimType, identity.RoleClaimType);
        var modifiedTicket = new AuthenticationTicket(modifiedIdentity, ticket.Properties);
        return _standardSerializers.Serialize(modifiedTicket);
    }

    public AuthenticationTicket Deserialize(byte[] data)
    {
        var ticket = _standardSerializers.Deserialize(data);
        var identity = ticket.Identity;
        var otherClaims = identity.Claims.Where(c => c.Type != RoleBitMaskType);
        var encodedRoleClaim = identity.Claims.SingleOrDefault(c => c.Type == RoleBitMaskType);
        if (encodedRoleClaim == null)
            return ticket;

        var roleClaims = DecodeRoles(encodedRoleClaim.Value).Select(r => new Claim(identity.RoleClaimType, r));
        var modifiedClaims = otherClaims.Concat(roleClaims);
        var modifiedIdentity = new ClaimsIdentity(modifiedClaims, identity.AuthenticationType, identity.NameClaimType, identity.RoleClaimType);
        return new AuthenticationTicket(modifiedIdentity, ticket.Properties);
    }
}
  1. В вашем Startup.cs настройте OWIN, чтобы использовать свой собственный формат, например:
var customTicketFormat = CustomTicketSerializer.CreateCustomTicketFormat(app);
OAuthBearerOptions.AccessTokenFormat = customTicketFormat;
OAuthServerOptions.AccessTokenFormat = customTicketFormat;
  1. В OAuthAuthorizationServerProvider добавить ClaimTypes.Role в ClaimsIdentity для каждой роли, назначенной пользователю.

  2. В вашем контроллере используйте стандартный AuthorizeAttribute, например

    [Authorize(Roles = "Role1")]
    [Route("")]
    public IHttpActionResult Get()
    

Для удобства и некоторой безопасности вы можете подклассифицировать класс AuthorizeAttribute для принятия CustomRoles enum вместо строки в качестве конфигурации роли.

Ответ 2

Надеюсь, я правильно ответил на ваши вопросы и могу дать некоторые ответы:

1) Вы можете наложить деньги, если вы разработали свою AS, чтобы требовать проверки ее каждый раз, когда пользователь входил в систему.

2) Я думаю, что @Hans Z. означает отмену пользователя AS. Когда RS отменяет пользователя, он не изменяет того факта, что они все еще идентифицированы AS. Но когда AS отменяет пользователя, это предотвращает их использование их идентичности.

3) В статье, вероятно, предполагается, что авторизация выполняется RS, AS несет ответственность только за то, чтобы рассказать вам, кто является пользователем, и RS должен решить авторизацию на основе этого.

Ответ 3

Основным преимуществом подхода токена обновления является сокращение числа запросов к базе данных, токен доступа имеет претензии и подписан, поэтому токен может быть доверен без запроса базы данных.

Кэширование доступа к токену будет работать, но тогда вам придется запрашивать кеш при каждом запросе.

Это компромисс между вами, вы должны выбрать между n минутами задержки изменений прав доступа и количеством запросов для проверки действительности токена доступа.

С дополнительной сложностью вы можете почти добиться того, что в этом случае вам придется хранить кеш в ОЗУ сервера и хранить только отозванные маркеры, чтобы сохранить список небольшим. Сложность возникает, когда у вас есть несколько экземпляров серверов, которые вам нужно будет хранить в кеше с отмененными токенами в синхронизации между вашими RS и AS.

В основном, когда токен доступа отменяется, AS должна будет сообщить всем RS, чтобы добавить этот токен доступа в отброшенный кэш маркера.

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

Ответ 4

"Сервер авторизации может быть тем же сервером, что и сервер ресурсов, или отдельным объектом". [RFC 6749, p. 6]

С учетом вышесказанного вы могли бы кешировать, если бы это было так, но токен должен быть понятен для сервера ресурсов, и его не нужно кэшировать. Если это детали вашей реализации, так что да, кеширование возможно, но не обязательно.