首页 文章

OWIN安全 - 如何实施OAuth2刷新令牌

提问于
浏览
71

我正在使用Visual Studio 2013附带的Web Api 2模板,它有一些OWIN中间件来进行用户身份验证等 .

OAuthAuthorizationServerOptions 中,我注意到OAuth2服务器设置为分发在14天后到期的令牌

OAuthOptions = new OAuthAuthorizationServerOptions
 {
      TokenEndpointPath = new PathString("/api/token"),
      Provider = new ApplicationOAuthProvider(PublicClientId,UserManagerFactory) ,
      AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
      AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
      AllowInsecureHttp = true
 };

这不适合我的最新项目 . 我想发布短暂的bearer_tokens,可以使用 refresh_token 刷新

我做了很多谷歌搜索,找不到任何有用的东西 .

所以这就是我设法得到的 . 我现在已经达到了“WTF我现在”的地步 .

我写了 RefreshTokenProvider ,根据 OAuthAuthorizationServerOptions 类的 RefreshTokenProvider 属性实现了 IAuthenticationTokenProvider

public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
    {
       private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();

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


            _refreshTokens.TryAdd(guid, context.Ticket);

            // hash??
            context.SetToken(guid);
        }

        public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
        {
            AuthenticationTicket ticket;

            if (_refreshTokens.TryRemove(context.Token, out ticket))
            {
                context.SetTicket(ticket);
            }
        }

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

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

    // Now in my Startup.Auth.cs
    OAuthOptions = new OAuthAuthorizationServerOptions
    {
        TokenEndpointPath = new PathString("/api/token"),
        Provider = new ApplicationOAuthProvider(PublicClientId,UserManagerFactory) ,
        AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
        AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(2),
        AllowInsecureHttp = true,
        RefreshTokenProvider = new RefreshTokenProvider() // This is my test
    };

所以现在有人请求 bearer_token 我现在正在发送 refresh_token ,这很棒 .

那么现在我如何使用这个refresh_token来获取一个新的 bearer_token ,可能我需要向我的令牌 endpoints 发送一个请求,并设置一些特定的HTTP头?

在我输入时大声思考......我应该在 SimpleRefreshTokenProvider 中处理refresh_token到期吗?客户如何获得新的 refresh_token

我真的可以使用一些阅读材料/文档,因为我不想弄错,并希望遵循某种标准 .

4 回答

  • 1

    刚刚使用Bearer实现了我的OWIN服务(在下面称为access_token)和Refresh Tokens . 我对此的见解是,您可以使用不同的流程 . 因此,它取决于您要使用的流程如何设置access_token和refresh_token到期时间 .

    我将在后面描述两个 flows AB (我建议你想要的是流程B):

    A) access_token和refresh_token的到期时间与默认的1200秒或20分钟相同 . 此流程需要您的客户端首先发送带有登录数据的client_id和client_secret以获取access_token,refresh_token和expiration_time . 使用refresh_token,现在可以获得新的access_token 20分钟(或者在OAuthAuthorizationServerOptions中设置AccessTokenExpireTimeSpan的任何内容) . 由于access_token和refresh_token的到期时间相同,您的客户端有责任在到期时间之前获取新的access_token!例如 . 您的客户端可以使用正文向您的令牌 endpoints 发送刷新POST调用(注释:您应该在 生产环境 中使用https)

    grant_type=refresh_token&client_id=xxxxxx&refresh_token=xxxxxxxx-xxxx-xxxx-xxxx-xxxxx
    

    在例如获得新令牌之后19分钟,以防止令牌到期 .

    B) 在此流程中,您希望access_token的短期到期以及refresh_token的长期到期 . 让我们假设出于测试目的,您将access_token设置为在10秒内过期( AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10) ),并将refresh_token设置为5分钟 . 现在谈到设置refresh_token的到期时间的有趣部分:你在SimpleRefreshTokenProvider类的createAsync函数中执行此操作,如下所示:

    var guid = Guid.NewGuid().ToString();
    
    
            //copy properties and set the desired lifetime of refresh token
            var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
            {
                IssuedUtc = context.Ticket.Properties.IssuedUtc,
                ExpiresUtc = DateTime.UtcNow.AddMinutes(5) //SET DATETIME to 5 Minutes
                //ExpiresUtc = DateTime.UtcNow.AddMonths(3) 
            };
            /*CREATE A NEW TICKET WITH EXPIRATION TIME OF 5 MINUTES 
             *INCLUDING THE VALUES OF THE CONTEXT TICKET: SO ALL WE 
             *DO HERE IS TO ADD THE PROPERTIES IssuedUtc and 
             *ExpiredUtc to the TICKET*/
            var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);
    
            //saving the new refreshTokenTicket to a local var of Type ConcurrentDictionary<string,AuthenticationTicket>
            // consider storing only the hash of the handle
            RefreshTokens.TryAdd(guid, refreshTokenTicket);            
            context.SetToken(guid);
    

    现在,当 access_token 过期时,您的客户端能够使用refresh_token向您的令牌 endpoints 发送POST调用 . 通话的正文部分可能如下所示: grant_type=refresh_token&client_id=xxxxxx&refresh_token=xxxxxxxx-xxxx-xxxx-xxxx-xx

    一个重要的事情是,您可能不仅要在CreateAsync函数中使用此代码,还要在Create函数中使用此代码 . 因此,您应该考虑使用您自己的函数(例如,名为CreateTokenInternal)来实现上述代码 . Here you can find implementations of different flows including refresh_token flow(但没有设置refresh_token的到期时间)

    Here is one sample implementation of IAuthenticationTokenProvider on github(设置了refresh_token的到期时间)

    很抱歉,我无法提供比OAuth规范和Microsoft API文档更多的材料 . 我会在这里发布链接,但我的声誉不允许我发布超过2个链接....

    我希望这可以帮助其他人在尝试使用与access_token到期时间不同的refresh_token到期时间来实现OAuth2.0时节省时间 . 我在网上找不到一个示例实现(除了上面链接的一个thinktecture),它花了我几个小时的调查,直到它对我有用 .

    新信息:在我的情况下,我有两种不同的可能性来接收令牌 . 一种是接收有效的access_token . 在那里,我必须发送一个带有String主体的POST调用,格式为application / x-www-form-urlencoded,其中包含以下数据

    client_id=YOURCLIENTID&grant_type=password&username=YOURUSERNAME&password=YOURPASSWORD
    

    第二,如果access_token不再有效,我们可以通过发送一个带有String体的POST调用来尝试refresh_token,格式为 application/x-www-form-urlencoded ,带有以下数据 grant_type=refresh_token&client_id=YOURCLIENTID&refresh_token=YOURREFRESHTOKENGUID

  • 38

    你需要实现 RefreshTokenProvider . 首先为RefreshTokenProvider创建类,即 .

    public class ApplicationRefreshTokenProvider : AuthenticationTokenProvider
    {
        public override void Create(AuthenticationTokenCreateContext context)
        {
            // Expiration time in seconds
            int expire = 5*60;
            context.Ticket.Properties.ExpiresUtc = new DateTimeOffset(DateTime.Now.AddSeconds(expire));
            context.SetToken(context.SerializeTicket());
        }
    
        public override void Receive(AuthenticationTokenReceiveContext context)
        {
            context.DeserializeTicket(context.Token);
        }
    }
    

    然后将实例添加到 OAuthOptions .

    OAuthOptions = new OAuthAuthorizationServerOptions
    {
        TokenEndpointPath = new PathString("/authenticate"),
        Provider = new ApplicationOAuthProvider(),
        AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(expire),
        RefreshTokenProvider = new ApplicationRefreshTokenProvider()
    };
    
  • 8

    我不认为你应该使用数组来维护令牌 . 你也不需要guid作为代币 .

    您可以轻松使用context.SerializeTicket() .

    请参阅下面的代码 .

    public class RefreshTokenProvider : IAuthenticationTokenProvider
    {
        public async Task CreateAsync(AuthenticationTokenCreateContext context)
        {
            Create(context);
        }
    
        public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
        {
            Receive(context);
        }
    
        public void Create(AuthenticationTokenCreateContext context)
        {
            object inputs;
            context.OwinContext.Environment.TryGetValue("Microsoft.Owin.Form#collection", out inputs);
    
            var grantType = ((FormCollection)inputs)?.GetValues("grant_type");
    
            var grant = grantType.FirstOrDefault();
    
            if (grant == null || grant.Equals("refresh_token")) return;
    
            context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(Constants.RefreshTokenExpiryInDays);
    
            context.SetToken(context.SerializeTicket());
        }
    
        public void Receive(AuthenticationTokenReceiveContext context)
        {
            context.DeserializeTicket(context.Token);
    
            if (context.Ticket == null)
            {
                context.Response.StatusCode = 400;
                context.Response.ContentType = "application/json";
                context.Response.ReasonPhrase = "invalid token";
                return;
            }
    
            if (context.Ticket.Properties.ExpiresUtc <= DateTime.UtcNow)
            {
                context.Response.StatusCode = 401;
                context.Response.ContentType = "application/json";
                context.Response.ReasonPhrase = "unauthorized";
                return;
            }
    
            context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(Constants.RefreshTokenExpiryInDays);
            context.SetTicket(context.Ticket);
        }
    }
    
  • 72

    Freddy's answer帮助我做了很多工作 . 为了完整起见,您可以在此处实现令牌的哈希:

    private string ComputeHash(Guid input)
    {
        byte[] source = input.ToByteArray();
    
        var encoder = new SHA256Managed();
        byte[] encoded = encoder.ComputeHash(source);
    
        return Convert.ToBase64String(encoded);
    }
    

    CreateAsync

    var guid = Guid.NewGuid();
    ...
    _refreshTokens.TryAdd(ComputeHash(guid), refreshTokenTicket);
    context.SetToken(guid.ToString());
    

    ReceiveAsync

    public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
    {
        Guid token;
    
        if (Guid.TryParse(context.Token, out token))
        {
            AuthenticationTicket ticket;
    
            if (_refreshTokens.TryRemove(ComputeHash(token), out ticket))
            {
                context.SetTicket(ticket);
            }
        }
    }
    

相关问题