OWIN 安全 - 如何实现 OAuth2 刷新令牌 [英] OWIN Security - How to Implement OAuth2 Refresh Tokens

查看:39
本文介绍了OWIN 安全 - 如何实现 OAuth2 刷新令牌的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

这不适合我最近的项目.我想分发可以使用 refresh_token

刷新的短期bearer_tokens

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

所以这是我设法达到的程度.我现在已经达到了WTF do I now"的地步.

我编写了一个 RefreshTokenProvider,它根据 OAuthAuthorizationServerOptions 类上的 RefreshTokenProvider 属性实现了 IAuthenticationTokenProvider:>

 公共类 SimpleRefreshTokenProvider : IAuthenticationTokenProvider{私有静态 ConcurrentDictionary_refreshTokens = new ConcurrentDictionary();公共异步任务 CreateAsync(AuthenticationTokenCreateContext 上下文){var guid = Guid.NewGuid().ToString();_refreshTokens.TryAdd(guid, context.Ticket);//哈希??context.SetToken(guid);}公共异步任务 ReceiveAsync(AuthenticationTokenReceiveContext context){AuthenticationTicket 票证;if (_refreshTokens.TryRemove(context.Token, out ticket)){上下文.SetTicket(票);}}公共无效创建(AuthenticationTokenCreateContext 上下文){抛出新的 NotImplementedException();}公共无效接收(AuthenticationTokenReceiveContext 上下文){抛出新的 NotImplementedException();}}//现在在我的 Startup.Auth.csOAuthOptions = 新的 OAuthAuthorizationServerOptions{TokenEndpointPath = new PathString("/api/token"),Provider = new ApplicationOAuthProvider(PublicClientId,UserManagerFactory) ,AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(2),AllowInsecureHttp = 真,RefreshTokenProvider = new RefreshTokenProvider()//这是我的测试};

所以现在当有人请求 bearer_token 时,我现在发送的是 refresh_token,这很棒.

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

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

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

解决方案

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

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

A) access_token 和 refresh_token 的过期时间与默认的 1200 秒或 20 分钟相同.此流程需要您的客户端首先发送带有登录数据的 client_id 和 client_secret,以获取 access_token、refresh_token 和 expire_time.使用 refresh_token 现在可以获得一个新的 access_token 20 分钟(或任何你在 OAuthAuthorizationServerOptions 中设置的 AccessTokenExpireTimeSpan ).因为access_token和refresh_token的过期时间是一样的,你的客户端有责任在过期时间之前拿到一个新的access_token!例如.您的客户端可以使用正文向您的令牌端点发送刷新 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();//复制属性并设置刷新令牌的期望生命周期var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary){IssuedUtc = context.Ticket.Properties.IssuedUtc,ExpiresUtc = DateTime.UtcNow.AddMinutes(5)//将日期时间设置为5分钟//ExpiresUtc = DateTime.UtcNow.AddMonths(3)};/*创建一个有效期为 5 分钟的新票证*包括上下文门票的价值:所以我们* 做这里是为了添加属性 IssuedUtc 和*ExpiredUtc 到 TICKET*/var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);//将新的 refreshTokenTicket 保存到 ConcurrentDictionary 类型的本地变量中//考虑只存储句柄的哈希值RefreshTokens.TryAdd(guid, refreshTokenTicket);context.SetToken(guid);

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

一件重要的事情是,您可能希望不仅在 CreateAsync 函数中而且在 Create 函数中使用此代码.所以你应该考虑对上面的代码使用你自己的函数(例如称为 CreateTokenInternal).在这里你可以找到不同流的实现,包括refresh_token流(但不设置refresh_token的过期时间)

这是 github 上 IAuthenticationTokenProvider 的一个示例实现(设置refresh_token的过期时间)

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

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

新信息:就我而言,我有两种不同的可能性来接收令牌.一种是接收有效的 access_token.在那里,我必须使用以下数据以 application/x-www-form-urlencoded 格式发送带有字符串正文的 POST 调用

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

其次是如果 access_token 不再有效,我们可以通过发送带有字符串主体的 POST 调用来尝试 refresh_token,格式为 application/x-www-form-urlencoded 并带有以下数据 grant_type=refresh_token&client_id=YOURCLIENTID&refresh_token=YOURREFRESHTOKENGUID

I am using the Web Api 2 template that comes with Visual Studio 2013 has some OWIN middleware to do User Authentication and the likes of.

In the OAuthAuthorizationServerOptions I noticed that the OAuth2 Server is setup to hand out tokens that expire in 14 days

 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
 };

This is not suitable for my latest project. I would like to hand out short lived bearer_tokens that can be refreshed using a refresh_token

I have done lots of googling and can't find anything helpful.

So this is how far I have managed to get. I have now reached the point of "WTF do I now".

I have written a RefreshTokenProvider that implements IAuthenticationTokenProvider as per the RefreshTokenProvider property on OAuthAuthorizationServerOptions class:

    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
    };

So now when someone requests a bearer_token I am now sending a refresh_token, which is great.

So now how do I uses this refresh_token to get a new bearer_token, presumably I need to send a request to my token endpoint with some specific HTTP Headers set?

Just thinking out loud as I type... Should I handle refresh_token expiration in my SimpleRefreshTokenProvider? How would a client obtain a new refresh_token?

I could really do with some reading material / documentation because I don't want to get this wrong and would like to follow some sort of standard.

解决方案

Just implemented my OWIN Service with Bearer (called access_token in the following) and Refresh Tokens. My insight into this is that you can use different flows. So it depends on the flow you want to use how you set your access_token and refresh_token expiration times.

I will describe two flows A and B in the follwing (I suggest what you want to have is flow B):

A) expiration time of access_token and refresh_token are the same as it is per default 1200 seconds or 20 minutes. This flow needs your client first to send client_id and client_secret with login data to get an access_token, refresh_token and expiration_time. With the refresh_token it is now possible to get a new access_token for 20 minutes (or whatever you set the AccessTokenExpireTimeSpan in the OAuthAuthorizationServerOptions to). For the reason that the expiration time of access_token and refresh_token are the same, your client is responsible to get a new access_token before the expiration time! E.g. your client could send a refresh POST call to your token endpoint with the body (remark: you should use https in production)

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

to get a new token after e.g. 19 minutes to prevent the tokens from expiration.

B) in this flow you want to have a short term expiration for your access_token and a long term expiration for your refresh_token. Lets assume for test purpose you set the access_token to expire in 10 seconds (AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10)) and the refresh_token to 5 Minutes. Now it comes to the interesting part setting the expiration time of refresh_token: You do this in your createAsync function in SimpleRefreshTokenProvider class like this:

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);

Now your client is able to send a POST call with a refresh_token to your token endpoint when the access_token is expired. The body part of the call may look like this: grant_type=refresh_token&client_id=xxxxxx&refresh_token=xxxxxxxx-xxxx-xxxx-xxxx-xx

One important thing is that you may want to use this code not only in your CreateAsync function but also in your Create function. So you should consider to use your own function (e.g. called CreateTokenInternal) for the above code. Here you can find implementations of different flows including refresh_token flow(but without setting the expiration time of the refresh_token)

Here is one sample implementation of IAuthenticationTokenProvider on github (with setting the expiration time of the refresh_token)

I am sorry that I can't help out with further materials than the OAuth Specs and the Microsoft API Documentation. I would post the links here but my reputation doesn't let me post more than 2 links....

I hope this may help some others to spare time when trying to implement OAuth2.0 with refresh_token expiration time different to access_token expiration time. I couldn't find an example implementation on the web (except the one of thinktecture linked above) and it took me some hours of investigation until it worked for me.

New info: In my case I have two different possibilities to receive tokens. One is to receive a valid access_token. There I have to send a POST call with a String body in format application/x-www-form-urlencoded with the following data

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

Second is if access_token is not valid anymore we can try the refresh_token by sending a POST call with a String body in format application/x-www-form-urlencoded with the following data grant_type=refresh_token&client_id=YOURCLIENTID&refresh_token=YOURREFRESHTOKENGUID

这篇关于OWIN 安全 - 如何实现 OAuth2 刷新令牌的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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