通过提供代理令牌处理来自 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 的长不记名令牌的主要内容,如果未能解决你的问题,请参考以下文章

用于多个 WebApi 端点的 Azure AD 不记名令牌?

使用 OAuth 令牌生成 WebApi 将更多数据返回给带有不记名令牌的客户端

为多个 API 调用重用不记名令牌

Spring zuul代理不接受不记名令牌

修改 OWIN OAuth 中间件以使用 JWT 不记名令牌

即使在 asp.net core 5.0 中提供了不记名令牌,也会返回 401 [关闭]