通过提供代理令牌处理来自 webapi 的长不记名令牌

Posted

技术标签:

【中文标题】通过提供代理令牌处理来自 webapi 的长不记名令牌【英文标题】:Dealing with long bearer tokens from webapi by providing a surrogate token 【发布时间】:2015-02-06 07:22:14 【问题描述】:

我正在使用声明身份验证使用 ASP.NET WebApi 2 构建一个 Web api,我的用户可以拥有大量声明。由于有大量声明,不记名令牌会迅速增长,因此我正在尝试找到一种方法来返回更短的不记名令牌。

到目前为止,我发现我可以为 OAuth 选项 OAuthAuthorizationServerOptions.AccessTokenProvider 属性提供 IAuthenticationTokenProvider

OAuthOptions = new OAuthAuthorizationServerOptions

    TokenEndpointPath = new PathString("/Token"),
    Provider = new ApplicationOAuthProvider(PublicClientId),
    AccessTokenExpireTimeSpan = TimeSpan.FromHours(12),
    AccessTokenProvider = new GuidProvider() // <-- here
;

这让我有机会拦截 AuthenticationTicket 并将其隐藏起来,用更简单的东西代替它 - 在我下面的哈希 guid 示例中。 (注意:目前这个类只是在我的会话中保存一个ConcurrentDictionary&lt;string,AuthenticationTicket&gt; - 在一个真实的示例中,我打算将会话存储在一些持久性存储中)

public class GuidProvider : IAuthenticationTokenProvider

    private static ConcurrentDictionary<string, AuthenticationTicket> tokens 
        = new ConcurrentDictionary<string, AuthenticationTicket>();

    public void Create(AuthenticationTokenCreateContext context)
    
        throw new NotImplementedException();
    

    public async System.Threading.Tasks.Task CreateAsync(AuthenticationTokenCreateContext context)
    
        var guid = Guid.NewGuid().ToString();

        var ticket = Crypto.Hash(guid);

        tokens.TryAdd(ticket, context.Ticket);

        context.SetToken(ticket);
    

    public void Receive(AuthenticationTokenReceiveContext context)
    
        throw new NotImplementedException();
    

    public async System.Threading.Tasks.Task ReceiveAsync(AuthenticationTokenReceiveContext context)
    
        AuthenticationTicket ticket;

        if (tokens.TryGetValue(context.Token, out ticket))
        
            if (ticket.Properties.ExpiresUtc.Value < DateTime.UtcNow)
            
                tokens.TryRemove(context.Token, out ticket);
            
            context.SetTicket(ticket);
        
    

所以我的问题:

这是提供代理密钥来代替我的长声明生成令牌的适当(且安全!)方式吗? 我应该在 webapi/OAuth 堆栈中执行此操作的地方是否可能更好/更容易?

要注意的另一件事是我打算支持刷新令牌,实际上上面的示例是从使用这种机制的示例中提取的刷新令牌-除了刷新令牌它们似乎是一次性使用的,所以ReceiveAsync 方法通常会删除ConcurrentDictionary 提供的刷新令牌,我不完全确定我理解为什么?

【问题讨论】:

因为授权可以与客户端应用程序隔离,您无法从代理令牌推断出声明。 @jamiec 我使用 JWT 的回答是否帮助您缩短了充满声明的访问令牌? @TaiseerJoudeh - 有也没有。虽然它没有直接回答我的问题,但它给了我一个新的探索途径,我几乎没有遇到过。感谢您的回答,我希望此时可以接受一半的赏金。我可能会考虑在新的一年里对这个问题进行奖励。 【参考方案1】:

我不建议这样做,因为您最终会将身份验证票证存储到数据库或 Redis 服务器中,这里的缺点是,对于每个包含不记名令牌的请求,您将按顺序检查此永久存储解决 Guid 并再次获取票证以构建它。

我建议您使用 JSON Web Token JWT 而不是默认的不记名访问令牌格式,为此您需要在 @987654326 中的属性 Provider 中实现自定义访问令牌格式 CustomOAuthProvider @ 如下代码:

 OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
        
            //For Dev enviroment only (on production should be AllowInsecureHttp = false)
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/oauth2/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
            Provider = new CustomOAuthProvider(),
            AccessTokenFormat = new CustomJwtFormat("http://jwtauthzsrv.azurewebsites.net")
        ;

我注意到,向 JWT 令牌添加更多声明不会像默认访问令牌格式那样显着增加其大小。

在一个包含 2 个 JWT 的样本下方,每个 JWT 中都有不同的声明,第二个比第一个大 50 个字符。我建议您使用jwt.io 检查每个编码的内容 第一个智威汤逊:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1bmlxdWVfbmFtZSI6InRhaXNlZXIiLCJzdWIiOiJ0YWlzZWVyIiwicm9sZSI6WyJNYW5hZ2VyIiwiU3VwZXJ2aXNvciJdLCJpc3MiOiJodHRwOi8vand0YXV0aHpzcnYuYXp1cmV3ZWJzaXRlcy5uZXQiLCJhdWQiOiIwOTkxNTNjMjYyNTE0OWJjOGVjYjNlODVlMDNmMDAyMiIsImV4cCI6MTQxODY0NzMyNywibmJmIjoxNDE4NjQ1NTI3fQ.vH9XPtjtAv2-6SwlyX4fKNJfm5ZTVHd_9a3bRgkA_LI

第二个 JWT(更多声明):

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1bmlxdWVfbmFtZSI6InRhaXNlZXIiLCJzdWIiOiJ0YWlzZWVyIiwicm9sZSI6WyJNYW5hZ2VyIiwiU3VwZXJ2aXNvciIsIlN1cGVydmlzb3IxIiwiU3VwZXJ2aXNvcjIiLCJTdXBlcnZpc29yMyJdLCJpc3MiOiJodHRwOi8vand0YXV0aHpzcnYuYXp1cmV3ZWJzaXRlcy5uZXQiLCJhdWQiOiIwOTkxNTNjMjYyNTE0OWJjOGVjYjNlODVlMDNmMDAyMiIsImV4cCI6MTQxODY0NzQ1NiwibmJmIjoxNDE4NjQ1NjU2fQ.TFEGDtz1RN8VmCQu7JH4Iug0B8UlWDLVrIlvc-7IK3E

JWT 格式正在成为颁发 OAuth 2.0 不记名令牌的标准方式,它也将与刷新令牌授权一起使用。但请记住,JWT 只是签名令牌,并没有像默认访问令牌格式那样加密,所以不要在其中存储机密数据。

我已经在 bitoftech.net 上写了 detailed blog post 关于如何在 ASP.NET Web API 中使用 JWT 令牌以及现场演示 API 和 source code on GIthub,如果您需要更多信息,请随时查看并告诉我帮助。

祝你好运!

【讨论】:

您提到 JWT 使用 refresh_tokens。你能详细说明吗?现在,我有一个实现,它将给我一个 JWT 作为我的 access_token,并且在同一个响应中,它包含一个小得多的令牌(一个 guid),即我的 refresh_token。这是正确的过程还是 refresh_token 也应该是寿命更长的 JWT? 不,刷新令牌只是存储在数据库中的受保护票证的标识符,您使用 grant_type=refresh_token 表示此标识符,如果它有效(未从数据库中删除且未过期),您将收到新的 JWT 访问令牌,希望清楚。 这是有道理的。谢谢。

以上是关于通过提供代理令牌处理来自 webapi 的长不记名令牌的主要内容,如果未能解决你的问题,请参考以下文章