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

我使用的是 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
};

这不适合我最近的项目。我想分发一些短命的 berer _ tokens,它们可以使用 refresh_token进行刷新

我谷歌了很多次,但是没有找到任何有用的东西。

这就是我的成果。我现在已经到了“我现在卧槽”的地步。

我已经编写了一个 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 Header 设置的请求呢?

只是在输入时大声地思考... ... 我应该在 SimpleRefreshTokenProvider中处理 resh _ token 过期吗?客户如何获得新的 refresh_token

我真的可以做一些阅读材料/文档,因为我不想得到这个错误,并希望遵循某种标准。

127454 次浏览

刚刚实现了我的 OWIN 服务与承载(在下面称为访问 _ 令牌)和刷新令牌。我对此的见解是,您可以使用不同的流。因此,它取决于您希望如何设置 access _ token 和 resh _ token 过期时间的流。

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

A) access _ token 和 refresh _ token 的过期时间与默认值1200秒或20分钟的过期时间相同。这个流需要您的客户端首先发送 client _ id 和 client _ secret 以及登录数据,从而获得一个 access _ token、 refresh _ token 和失效时间。使用 refresh _ token,现在可以在20分钟内获得一个新的 access _ token (或者在 OAuthorizationServerOptions 中设置 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 设置短期过期期限,为 resh _ token 设置长期过期期限。让我们假设出于测试目的,您将 access _ token 设置为10秒后到期(AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(10)) ,将 resh _ 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过期时,您的客户端能够使用 resh _ token 向您的令牌端点发送 POST 调用。调用的主体部分可能如下所示: grant_type=refresh_token&client_id=xxxxxx&refresh_token=xxxxxxxx-xxxx-xxxx-xxxx-xx

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

下面是 github 上 IAuthenticationTokenProvider 的一个示例实现(设置 refresh _ token 的过期时间)

很抱歉,除了 OAuth 规范和 MicrosoftAPI 文档之外,我无法帮助您了解更多的材料。我想张贴在这里的链接,但我的声誉不让我张贴超过2个链接..。

我希望这可以帮助其他人在尝试实现 OAuth2.0时腾出时间,刷新 _ 令牌过期时间不同于访问 _ 令牌过期时间。我无法在网络上找到一个实现的例子(除了上面链接的一个架构) ,它花了我几个小时的调查,直到它为我工作。

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

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

第二个问题是,如果 access _ token 不再有效,我们可以使用以下数据 grant_type=refresh_token&client_id=YOURCLIENTID&refresh_token=YOURREFRESHTOKENGUID发送一个带字符串主体的 POST 调用来尝试 refresh _ token

您需要实现 刷新令牌提供程序。 首先为 RefreshTokenProvider ie 创建类。

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

我认为您不应该使用数组来维护令牌。你也不需要一个向导作为信物。

您可以很容易地使用上下文。

请参阅我的下面代码。

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