为什么在 oauth2 中缓存访问令牌被认为是不好的? [英] Why caching access token is consider bad in oauth2?

查看:26
本文介绍了为什么在 oauth2 中缓存访问令牌被认为是不好的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在关注这篇文章以撤销用户访问权限:

I am following this article for revoking user access :

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

现在考虑在验证用户之后我已经发布了一个生命周期为 30 分钟的访问令牌,如上篇文章所示,刷新令牌为 1 天,但是如果管理员在 10 分钟内删除该用户并且还剩下 20 分钟,那么现在在这种情况下会怎样我需要撤销该用户的访问权限.

Now consider after validating user I have issued an accesstoken with 30 minutes life span as shown in above article and with refresh token as 1 day but what if admin delete that user in 10 minutes with 20 minutes still left so now in this case I need to revoke access that user.

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

In order to do this I need to remove that user entry from refresh token table to disallow further access token request but as accesstoken expire time is still having 20 minutes so user would be able to access protected resource which is completely wrong.

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

So I was thinking to implement caching mechanism to cache the access token on server and also save in database. So when that user is revoked I can simply remove that user entry from cache and database to stop that user access from accessing protected resource.

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

But this 2 below answers are saying this is not how oauth2 was designed :

撤销 OAuthBearerAuthentication 的访问令牌

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

所以我的问题是:

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

1) Why caching access token is not considered better than refresh token mechanism and also a bad approach?

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

My second question is based on this below answer given by @Hans Z. in which he is saying that :

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

This necessarily would involve the Resource Server (RS) consulting the Authorization Server (AS) which is a huge overhead.

2) 在撤销用户访问权限的情况下,为什么 RS 会咨询 AS,因为 AS 仅用于根据此对用户进行身份验证并生成访问令牌 文章?

2) In case of revoking access for a user why does RS would consult AS because AS is just for authenticating user and generating access token as per this Article?

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

3) In the article there are only 2 projects :

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

  • Authentication.api - Authenticating user and generating access token
  • Resource server - validating accesstoken with the help of [Authorize] attribute

在上面的例子中,哪个是授权服务器呢?

In above case which is the authorisation server then?

更新:我决定使用刷新令牌来撤销用户访问,以防用户被删除,并且当用户注销时,我将从刷新令牌表中刷新令牌,因为您要求我们要注销用户点击退出后立即登录.

Update : I have decided to use refresh token to revoke user access in case user is deleted and also when user logout i will refresh token from refresh token table because of your requirement that we want to logout user immediately as soon as user clicks on logout.

但这里的问题是我有 250 个与用户关联的角色,所以如果我将角色放入 accesstoken 中,那么 accesstoken 的大小将非常巨大,我们无法从标题中传递如此巨大的 accesstoken 但我无法查询每次调用端点时验证用户访问权限的角色.

But here the problem is i have 250 roles associated with user so if i put roles in accesstoken then size of accesstoken will be so huge and we cannot pass such huge accesstoken from header but i cannot query roles for validating user access for endpoints each time that endpoint is called.

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

So this is 1 another problem which i am facing.

推荐答案

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

There seems to be 2 different questions here: about access token and about big list of roles.

访问令牌

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

OAuth2 was designed to be able to handle high load and this requires some trade-offs. Particularly this is the reason why OAuth2 explicitly separates "Resource Server" and "Authorization Server" roles on the one hand, and "access token" and "refresh token" on the other hand. If for each request you have to check user authorization, it means that your Authorization Server should be able to handle all requests in your system. For high load systems this is not feasible.

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

OAuth2 allows you to make the following trade-off between performance and security: Authorization Server generates an Access Token that can be verified by the Resource Server without accessing Authorization Server (either at all or at least not more than once for a life of Authorization Server). This is effectively caching of the authorization information. So in this way you can drastically reduce load on your Authorization Server. The drawback is again the same as always with caching: authorization information might get stall. By varying access token life time you can tune performance vs. security balance.

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

This approach also might help if you do micro-services architecture where each service has own storage and don't access each other's.

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

Still if you don't have much load and you only have single Resource Server rather than tons of different services implemented using different technologies, there is nothing that prohibits you from actually doing full-blown validation on every request. I.e. yes, you may store Access Token in the DB, verify it on every access to the Resource Server and remove all Access Tokens when user is deleted, etc. But as @Evk noticed, if this is your scenario - OAuth2 is an overshoot for you.

大量角色

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

AFAIU OAuth2 doesn't provide an explicit feature for user roles. There is "Scopes" feature that might be also used for roles and its typical implementation it will produce too long string for 250 roles. Still OAuth2 doesn't explicitly specify any particular format for access token, so you can create a custom token that will hold roles information as a bit mask. Using base-64 encoding you can get 6 roles into a single character (64 = 2^6). So 250-300 roles will be manageable 40-50 chars.

JWT

因为无论如何您可能都需要一些自定义令牌,所以您可能对 JSON Web 令牌 又名 JWT 感兴趣.简而言之,JWT 允许您指定自定义附加负载(私有声明)并将您的角色位掩码放在那里.

Since you'll probably need some custom token anyway, you might be interested in the JSON Web Tokens aka JWT. In short JWT lets you specify custom additional payload (Private claims) and put your roles bitmask there.

如果您真的不需要任何 OAuth2 高级功能(例如范围),您实际上可以单独使用 JWT,而无需使用完整的 OAuth2 内容.尽管 JWT 令牌应该仅通过这些内容进行验证,但您仍然可以将它们存储在本地数据库中,并对数据库进行额外的验证(就像您将要使用访问刷新令牌一样).

You may actually use JWT alone without whole OAuth2 stuff if you don't really need any of the OAuth2 advanced features (such as scopes). Although JWT-tokens are supposed to be validated just by theis contents only, you may still store them in your local DB and do additional validation against the DB (as you were going to do with access refresh token).

2017 年 12 月 1 日更新

如果您想使用 OWIN OAuth 基础架构,您可以通过 AccessTokenFormatOAuthBearerAuthenticationOptionsOAuthAuthorizationServerOptions.您也可以覆盖 RefreshTokenFormat.

If you want to use OWIN OAuth infrastructure, you can customize token format providing custom formatter via AccessTokenFormat in OAuthBearerAuthenticationOptions and OAuthAuthorizationServerOptions. You may also override RefreshTokenFormat.

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

Here is a sketch that shows how you can "compress" roles claims into a single bitmask:

  1. 定义您的 CustomRoles 枚举,列出您拥有的所有角色
  1. Define your CustomRoles enumeration that list all roles you have

[Flags]
public enum CustomRoles
{
    Role1,
    Role2,
    Role3,

    MaxRole // fake, for convenience
}

  1. 创建EncodeRolesDecodeRoles 方法以在IEnumerable 格式的角色和基于 的base64 编码位掩码之间进行转换上面定义的 CustomRoles 如:
  1. Create EncodeRoles and DecodeRoles methods to convert between IEnumerable<string> format for roles and base64-encoded bit mask based on CustomRoles defined above such as:

    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 的自定义实现中使用这些方法.为了在这个草图中简单起见,我将大部分工作委托给了标准的 OWIN 组件,并且只实现了我的 CustomTicketSerializer,它创建了另一个 AuthenticationTicket 并使用了标准的 DataSerializers.Ticket.这显然不是最有效的方法,但它显示了您可以做什么:
  1. Use those methods in a custom implementation of SecureDataFormat<AuthenticationTicket>. For simplicity in this sketch I delegate most of work to standard OWIN components and just implement my CustomTicketSerializer that creates another AuthenticationTicket and uses standard DataSerializers.Ticket. This is obviously not the most efficient way but it shows what you could do:

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 以使用您的自定义格式,例如:
  1. In your Startup.cs configure OWIN to use your custom format such as:

var customTicketFormat = CustomTicketSerializer.CreateCustomTicketFormat(app);
OAuthBearerOptions.AccessTokenFormat = customTicketFormat;
OAuthServerOptions.AccessTokenFormat = customTicketFormat;

  1. 在您的 OAuthAuthorizationServerProvider 中,为分配给用户的每个角色将 ClaimTypes.Role 添加到 ClaimsIdentity.

  1. In your OAuthAuthorizationServerProvider add ClaimTypes.Role to the ClaimsIdentity for each role assigned to the user.

在你的控制器中使用标准的AuthorizeAttribute,比如

In your controller use standard AuthorizeAttribute such as

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

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

For convenience and some safety you may subclass AuthorizeAttribute class to accept CustomRoles enum instead of string as role configuration.

这篇关于为什么在 oauth2 中缓存访问令牌被认为是不好的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆