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

Lea*_*sed 11 c# token oauth-2.0 asp.net-web-api owin

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

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

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

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

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

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

撤消OAuthBearerAuthentication的访问令牌

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

所以我的问题是:

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

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

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

2)在撤销对访问用户的情况下,为什么会RS AS因为仅仅是用于验证用户生成访问令牌按本咨询文章

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

  • Authentication.api - 验证用户并生成访问令牌
  • 资源服务器 - 借助[Authorize] 属性验证accesstoken

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

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

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

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

Ser*_*gGr 11

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

访问令牌

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

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

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

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

大角色列表

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

智威汤逊

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

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


2017年12月1日更新

如果您想使用OWIN OAuth基础架构,您可以自定义令牌格式,通过AccessTokenFormatin OAuthBearerAuthenticationOptions和提供自定义格式化程序OAuthAuthorizationServerOptions.你也可以覆盖RefreshTokenFormat.

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

  1. 定义CustomRoles列出所有角色的枚举
[Flags]
public enum CustomRoles
{
    Role1,
    Role2,
    Role3,

    MaxRole // fake, for convenience
}
Run Code Online (Sandbox Code Playgroud)
  1. 基于上面定义的创建EncodeRolesDecodeRoles方法在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;
    }
Run Code Online (Sandbox Code Playgroud)
  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);
    }
}
Run Code Online (Sandbox Code Playgroud)
  1. 在您的Startup.cs配置OWIN中使用您的自定义格式,例如:
var customTicketFormat = CustomTicketSerializer.CreateCustomTicketFormat(app);
OAuthBearerOptions.AccessTokenFormat = customTicketFormat;
OAuthServerOptions.AccessTokenFormat = customTicketFormat;
Run Code Online (Sandbox Code Playgroud)
  1. 在您OAuthAuthorizationServerProvider添加ClaimTypes.RoleClaimsIdentity分配给用户的每个角色中.

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

    [Authorize(Roles = "Role1")]
    [Route("")]
    public IHttpActionResult Get()
    
    Run Code Online (Sandbox Code Playgroud)

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