我正在使用 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
};
这不适合我的最新项目。我想分发可以使用 refresh_token
刷新的短期 Bearer_tokens
我做了很多谷歌搜索,找不到任何有用的东西。
所以这就是我设法达到的程度。我现在已经达到了“我现在做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
,大概我需要向我的令牌端点发送一个请求并设置一些特定的 HTTP 标头?
只是在我输入时大声思考...我应该在我的 SimpleRefreshTokenProvider
中处理 refresh_token 到期吗?客户如何获得新的 refresh_token
?
我真的可以使用一些阅读材料/文档,因为我不想弄错,并且想遵循某种标准。
刚刚使用 Bearer(以下称为 access_token)和 Refresh Tokens 实现了我的 OWIN 服务。我对此的见解是您可以使用不同的流程。因此,这取决于您要使用的流程如何设置 access_token 和 refresh_token 到期时间。
我将在下面描述两个流程 A 和 B(我建议您想要的是流程 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();
//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)。 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。在那里,我必须发送一个带有字符串正文的 POST 调用,格式为 application/x-www-form-urlencoded,并带有以下数据
client_id=YOURCLIENTID&grant_type=password&username=YOURUSERNAME&password=YOURPASSWORD
其次,如果 access_token 不再有效,我们可以通过发送带有格式为 application/x-www-form-urlencoded
的字符串主体的 POST 调用以及以下数据 grant_type=refresh_token&client_id=YOURCLIENTID&refresh_token=YOURREFRESHTOKENGUID
来尝试 refresh_token
您需要实现 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()
};
context.OwinContext.Environment
包含一个 Microsoft.Owin.Form#collection
键,它为您提供一个 FormCollection
,您可以在其中找到授权类型并相应地添加一个令牌。它正在泄漏实现,它可能会在未来的更新中随时中断,我不确定它是否可以在 OWIN 主机之间移植。
var form = await context.Request.ReadFormAsync();
var grantType = form.GetValue("grant_type");
然后如果授权类型不是“refresh_token”,则发出刷新令牌
我认为您不应该使用数组来维护令牌。您也不需要指南作为令牌。
您可以轻松使用 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);
}
}
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);
}
}
}
RefreshTokens
中,因此如果RefreshTokens
泄露,攻击者无法使用该信息!?