为什么缓存访问令牌在oauth2中被认为是错误的?

我正在关注此文章以撤消用户访问权限:

Enable OAuth Refresh Tokens in AngularJS App using ASP .NET Web API 2, and Owin

现在考虑validation用户后我已经发布了一个30分钟的生命周期的accessstoken,如上文所示,并且刷新令牌为1天,但如果管理员在10分钟内删除该用户20分钟仍然如此,那么现在我需要撤消该用户的访问权限。

为了做到这一点,我需要从刷新令牌表中删除该用户条目以禁止进一步的访问令牌请求,但由于accessstoken到期时间仍然是20分钟,因此用户将能够访问完全错误的受保护资源。

所以我想实现缓存机制来缓存服务器上的访问令牌并保存在数据库中 。 因此,当该用户被撤销时,我可以简单地从缓存和数据库中删除该用户条目,以阻止该用户访问访问受保护资源。

但是下面这两个答案说这不是oauth2的设计方式:

撤消OAuthBearerAuthentication的访问令牌

OAuth2 – 刷新令牌不必要的复杂性

所以我的问题是:

1)为什么缓存访问令牌不被认为比刷新令牌机制更好,也是一种糟糕的方法?

我的第二个问题是基于@Hans Z给出的以下答案,其中他说:

这必然涉及资源服务器(RS)咨询授权服务器(AS),这是一个巨大的开销。

2)如果撤销用户的访问权限,为什么RS会咨询AS,因为AS仅用于validation用户并根据本文生成访问令牌?

3)在文章中只有2个项目:

  • Authentication.api – validation用户并生成访问令牌
  • 资源服务器 – 借助[Authorize]属性validationaccesstoken

    在上述情况下,授权服务器呢?

更新:我决定使用刷新令牌撤消用户访问以防用户被删除,并且当用户注销时我将刷新令牌刷新令牌表因为您要求我们在用户点击注销后立即注销用户。

但这里的问题是我有250个与用户相关的角色,所以如果我把角色放在accesstoken中,那么accesstoken的大小将是如此巨大,我们无法从头部传递如此巨大的accessstoken但我无法查询角色以validation每次端点的用户访问权限调用端点。

所以这是我面临的另一个问题。

这里似乎有两个不同的问题:关于访问令牌和关于大的角色列表。

访问令牌

OAuth2旨在能够处理高负载,这需要一些权衡。 特别是这就是为什么OAuth2一方面明确地分离“资源服务器”和“授权服务器”角色,另一方面是“访问令牌”和“刷新令牌”的原因。 如果对于每个请求,您必须检查用户授权,这意味着您的授权服务器应该能够处理系统中的所有请求。 对于高负载系统,这是不可行的。

OAuth2允许您在性能和安全性之间进行以下权衡 :授权服务器生成一个访问令牌,资源服务器可以在不访问授权服务器的情况下对其进行validation(在授权服务器的生命周期内完全或至少不超过一次) )。 这有效地缓存了授权信息。 因此,通过这种方式,您可以大幅减少授权服务器上的负载。 缺点再次与缓存一样:授权信息可能会停滞。 通过改变访问令牌的生命周期,您可以调整性能与安全性平衡。

如果您使用微服务架构,这种方法也可能会有所帮助,其中每个服务都有自己的存储而且不能互相访问。

如果您没有太多负载,并且您只有单个资源服务器而不是使用不同技术实现的大量不同服务,那么没有什么能阻止您对每个请求进行全面validation。 也就是说,您可以在数据库中存储访问令牌,在每次访问资源服务器时validation它,并在删除用户时删除所有访问令牌等。但正如@Evk注意到的,如果这是您的情况 – OAuth2是一个过冲您。

大角色列表

AFAIU OAuth2不为用户角色提供显式function。 “范围”function可能也用于角色及其典型实现,它将为250个角色生成太长的字符串。 仍然OAuth2没有明确指定访问令牌的任何特定格式,因此您可以创建一个自定义令牌,将角色信息保存为位掩码。 使用base-64编码,您可以在单个字符中获得6个角色(64 = 2 ^ 6)。 所以250-300个角色可管理40-50个角色。

智威汤逊

既然你可能还需要一些自定义令牌,你可能会对JSON Web Tokens aka JWT感兴趣。 简而言之,JWT允许您指定自定义附加有效负载(私有声明)并将角色位掩码放在那里。

如果你真的不需要任何OAuth2高级function(例如作用域),你实际上可以单独使用JWT而不需要整个OAuth2。 虽然JWT-tokens只能通过isis内容进行validation,但您仍然可以将它们存储在本地数据库中,并对数据库进行额外的validation(就像您将要使用访问刷新令牌一样)。


2017年12月1日更新

如果要使用OWIN OAuth基础结构,可以通过OAuthBearerAuthenticationOptionsOAuthAuthorizationServerOptions AccessTokenFormat自定义提供自定义格式化程序的标记格式。 您也可以覆盖RefreshTokenFormat

这是一个草图,显示如何将角色声明“压缩”为单个位掩码:

  1. 定义列出您拥有的所有角色的CustomRoles枚举
 [Flags] public enum CustomRoles { Role1, Role2, Role3, MaxRole // fake, for convenience } 
  1. 创建EncodeRolesDecodeRoles方法,在角色的IEnumerable格式和基于上面定义的CustomRoles base64编码位掩码之间进行转换,例如:
  public static string EncodeRoles(IEnumerable 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 DecodeRoles(string encoded) { byte[] bitMask = Convert.FromBase64String(encoded); var values = Enum.GetValues(typeof(CustomRoles)).Cast().Where(r => r != CustomRoles.MaxRole); var roles = new List(); 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的自定义实现中使用这些方法。 为简单起见,我将大部分工作委托给标准的OWIN组件,并实现我的CustomTicketSerializer ,它创建另一个AuthenticationTicket并使用标准的DataSerializers.Ticket 。 这显然不是最有效的方式,但它显示了您可以做的事情:
 public class CustomTicketSerializer : IDataSerializer { public const string RoleBitMaskType = "RoleBitMask"; private readonly IDataSerializer _standardSerializers = DataSerializers.Ticket; public static SecureDataFormat CreateCustomTicketFormat(IAppBuilder app) { var tokenProtector = app.CreateDataProtector(typeof(OAuthAuthorizationServerMiddleware).Namespace, "Access_Token", "v1"); var customTokenFormat = new SecureDataFormat(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. OAuthAuthorizationServerProviderOAuthAuthorizationServerProvider添加到分配给用户的每个角色的ClaimsIdentity

  2. 在您的控制器中使用标准的AuthorizeAttribute

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

为方便起见,您可以将AuthorizeAttribute类子类化为接受CustomRoles枚举而不是字符串作为角色配置。

我希望我的问题是正确的,可以提供一些答案:

1)如果您开发了AS,您可以兑现它,以便每次用户登录时都要validation它。

2)我认为@Hans Z.意味着由AS撤销用户。 当RS撤销用户时,它不会改变它们仍然是AS标识的事实。 但是当AS撤销用户时,它会阻止他们使用自己的身份。

3)文章可能假设授权由RS完成,AS仅负责告诉您谁是用户,RS将根据该决定授权。

刷新令牌方法的主要优点是减少数据库查询的数量,访问令牌具有声明并签名,因此可以信任令牌而无需查询数据库。

缓存访问令牌将起作用,但您必须在每个请求上查询缓存。

这是一个权衡,您必须在访问权限更改的n分钟延迟与检查访问令牌有效性的查询数量之间进行选择

由于增加了复杂性,您几乎可以实现这两种情况,在这种情况下,您必须将缓存存储在服务器RAM中,并且仅存储已撤销的令牌以使列表保持较小。 当您拥有多个服务器实例时,必须保持撤销令牌的缓存在RS和AS之间保持同步。

基本上,当撤销访问令牌时,AS必须通知所有RS将该访问令牌添加到撤销的令牌缓存。

每当有资源请求时,RS将检查令牌是否被撤销,如果没有被撤销,则RS服务器资源。 这样,每个请求都有开销,但随着缓存在内存中,它会大大减少,并且与有效令牌的数量相比,撤销令牌的数量将非常少。