OWIN ASP.NET - Cant generate Access Token using Refresh Token if Access Token is expired
当我尝试在访问令牌过期之前使用刷新令牌生成访问令牌时,系统会生成一个新的令牌,并且一切正常。但是,如果访问令牌已过期,则请求返回
如果最后一个尚未到期,我如何阻止客户端使用相同的刷新令牌来请求新的访问令牌?
这是我的代码:
Startup.cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public partial class Startup { public void Configuration(IAppBuilder app) { app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions() { AllowInsecureHttp = true, TokenEndpointPath = new PathString("/token"), AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(5), Provider = new OAuthProvider(), RefreshTokenProvider = new RefreshTokenProvider() }); app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()); app.UseWebApi(config); } } |
OAuthProvider.cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public class OAuthProvider : OAuthAuthorizationServerProvider { public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { context.Validated(); return Task.FromResult<object>(null); } public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { if (context.UserName =="admin" && context.Password =="123456") { var claimsIdentity = new ClaimsIdentity(context.Options.AuthenticationType); claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName)); claimsIdentity.AddClaim(new Claim(ClaimTypes.Role,"Admin")); var ticket = new AuthenticationTicket(claimsIdentity, null); context.Validated(ticket); } return Task.FromResult<object>(null); } public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context) { context.Validated(context.Ticket); return Task.FromResult<object>(null); } } |
RefreshTokenProvider.cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class RefreshTokenProvider : AuthenticationTokenProvider { private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>(); public override Task CreateAsync(AuthenticationTokenCreateContext context) { var guid = Guid.NewGuid().ToString(); _refreshTokens.TryAdd(guid, context.Ticket); context.SetToken(guid); return Task.FromResult<object>(null); } public override Task ReceiveAsync(AuthenticationTokenReceiveContext context) { if (_refreshTokens.TryRemove(context.Token, out AuthenticationTicket ticket)) { context.SetTicket(ticket); } return Task.FromResult<object>(null); } } |
对不起,英语不好,希望您能理解!
编辑:
好吧,我修改了代码以实现数据库支持,并将"刷新令牌"到期时间设置为5分钟。出于测试目的,有效期很小。
结果是:
OAuthProvider.cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | public class OAuthProvider : OAuthAuthorizationServerProvider { public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) { context.Validated(); return Task.FromResult<object>(null); } public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) { try { var account = AccountRepository.Instance.GetByUsername(context.UserName); if (account != null && Global.VerifyHash(context.Password, account.Password)) { var claimsIdentity = new ClaimsIdentity(context.Options.AuthenticationType); claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, account.Username)); claimsIdentity.AddClaim(new Claim("DriverId", account.DriverId.ToString())); var newTicket = new AuthenticationTicket(claimsIdentity, null); context.Validated(newTicket); } } catch { } return Task.FromResult<object>(null); } public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context) { context.Validated(); return Task.FromResult<object>(null); } } |
RefreshTokenProvider.cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | public class RefreshTokenProvider : AuthenticationTokenProvider { public override Task CreateAsync(AuthenticationTokenCreateContext context) { var refreshToken = new TokenModel() { Subject = context.Ticket.Identity.Name, Token = GenerateToken(), IssuedUtc = DateTime.UtcNow, ExpiresUtc = DateTime.UtcNow.AddMinutes(5) }; context.Ticket.Properties.IssuedUtc = refreshToken.IssuedUtc; context.Ticket.Properties.ExpiresUtc = refreshToken.ExpiresUtc; refreshToken.Ticket = context.SerializeTicket(); try { TokenRepository.Instance.Insert(refreshToken); context.SetToken(refreshToken.Token); } catch { } return Task.FromResult<object>(null); } public override Task ReceiveAsync(AuthenticationTokenReceiveContext context) { try { var refreshToken = TokenRepository.Instance.Get(context.Token); if (refreshToken != null) { if (TokenRepository.Instance.Delete(refreshToken)) { context.DeserializeTicket(refreshToken.Ticket); } } } catch { } return Task.FromResult<object>(null); } private string GenerateToken() { HashAlgorithm hashAlgorithm = new SHA256CryptoServiceProvider(); byte[] byteValue = Encoding.UTF8.GetBytes(Guid.NewGuid().ToString("N")); byte[] byteHash = hashAlgorithm.ComputeHash(byteValue); return Convert.ToBase64String(byteHash); } } |
感谢支持!
似乎刷新令牌与访问令牌具有相同的到期时间。因此,您需要延长刷新令牌的到期时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public override Task CreateAsync(AuthenticationTokenCreateContext context) { var form = context.Request.ReadFormAsync().Result; var grantType = form.GetValues("grant_type"); if (grantType[0] !="refresh_token") { ... // One day int expire = 24 * 60 * 60; context.Ticket.Properties.ExpiresUtc = new DateTimeOffset(DateTime.Now.AddSeconds(expire)); } base.Create(context); } |
-更新-
我已更新代码以回答您评论中的问题。它超出了您的要求。但我认为这将有助于解释。
这完全取决于需求和选择的策略。解释代码:
每次发出访问令牌时,您还将点击这段代码,它将添加刷新令牌。在此策略中,仅当grant_type不是'refresh_token'时,我才会发出新的刷新令牌。这意味着刷新令牌在某些时候到期,用户必须再次登录。
在该示例中,用户必须每天再次登录。但是,如果用户在refresh_token过期之前登录,则会发出新的刷新令牌。这样,刷新令牌具有绝对到期时间,从而迫使用户每天至少登录一次。
如果要"过期",可以在每次发出访问令牌时添加刷新令牌。请注意,除非刷新令牌在刷新之前过期,否则用户可能永远不必再次登录。
无论如何,当grant_type不是refresh_token时,我都不会阻止用户创建访问令牌。因为那是用户交互。访问令牌的窗口非常有限(认为是几小时或几分钟,而不是几天),因此它将很快过期。您不想每次在标头中收到访问令牌时都对其进行验证,因为这意味着您需要为每个调用检查数据库。
而不是考虑使"旧"刷新令牌无效的策略。您可以将最后刷新令牌的哈希版本保存在数据库中。如果不匹配,则返回" invalid_grant"错误。