OWIN安全性 – 如何实现OAuth2刷新令牌

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

OAuthAuthorizationServerOptions我注意到OAuth2服务器设置为OAuthAuthorizationServerOptions在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 }; 

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

我做了大量的search,并找不到有用的东西。

所以这是我设法得到的。 我现在已经达到了“现在做我跆拳道”的地步。

我已经编写了一个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 ,据推测我需要发送一个请求到我的令牌端点与一些特定的HTTP头设置?

只是大声思考,因为我键入…我应该在我的SimpleRefreshTokenProvider处理refresh_token过期吗? 客户如何获得新的refresh_token

我真的可以做一些阅读材料/文件,因为我不想弄错这个,想遵循某种标准。

刚刚实施我的OWIN服务与承载(下面称为access_token)和刷新令牌。 我对此的洞察是你可以使用不同的stream程。 所以这取决于你想要如何设置你的access_token和refresh_token到期时间的stream程。

我将在下面描述两个stream程 AB (我build议你想要的是stream程B):

A) access_token和refresh_token的到期时间与默认的1200秒或20分钟相同。 此stream程需要您的客户端首先发送client_id和client_secret以及login数据以获取access_token,refresh_token和expiration_time。 使用refresh_token,现在可以在20分钟内获得新的access_token(或者将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)在这个stream程中,你希望你的access_token有一个短期的到期,你的refresh_token要有一个长期的到期。 为了testing目的,假设您将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将POST调用发送到您的令牌端点。 调用的正文部分可能如下所示: grant_type=refresh_token&client_id=xxxxxx&refresh_token=xxxxxxxx-xxxx-xxxx-xxxx-xx

一个重要的事情是,您可能不仅要在CreateAsync函数中使用此代码,还要在Create函数中使用此代码。 所以你应该考虑使用你自己的函数(例如CreateTokenInternal)来获得上面的代码。 在这里你可以find不同stream的实现,包括refresh_tokenstream (但不设置refresh_token的到期时间)

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

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

我希望这可能会帮助其他人腾出时间来尝试使用不同于access_token过期时间的refresh_token过期时间来实现OAuth2.0。 我在网上找不到一个示例实现(上面链接的思想除外),花了我几个小时的调查,直到它为我工作。

新信息:在我的情况下,我有两种不同的可能性来接收令牌。 一个是收到一个有效的access_token。 在那里,我必须发送一个string正文格式应用程序/ x-www-form-urlencoded POST调用与下列数据

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

其次,如果access_token不再有效,我们可以通过使用以下数据application/x-www-form-urlencoded格式的string正文发送POST调用来尝试refresh_token: grant_type=refresh_token&client_id=YOURCLIENTID&refresh_token=YOURREFRESHTOKENGUID

你需要实现RefreshTokenProvider 。 首先为RefreshTokenProvider创build类即。

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

我不认为你应该使用数组来维护令牌。 你也不需要一个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); } } 

弗雷迪的回答帮了我很多努力才得以实现。 为了完整起见,您可以如何实现令牌的哈希:

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