Ответ 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
}
- Создайте методы
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;
}
- Используйте эти методы в пользовательской реализации
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);
}
}
- В вашем
Startup.cs
настройте OWIN, чтобы использовать свой собственный формат, например:
var customTicketFormat = CustomTicketSerializer.CreateCustomTicketFormat(app);
OAuthBearerOptions.AccessTokenFormat = customTicketFormat;
OAuthServerOptions.AccessTokenFormat = customTicketFormat;
-
В
OAuthAuthorizationServerProvider
добавитьClaimTypes.Role
вClaimsIdentity
для каждой роли, назначенной пользователю. -
В вашем контроллере используйте стандартный
AuthorizeAttribute
, например[Authorize(Roles = "Role1")] [Route("")] public IHttpActionResult Get()
Для удобства и некоторой безопасности вы можете подклассифицировать класс AuthorizeAttribute
для принятия CustomRoles
enum вместо строки в качестве конфигурации роли.