OWIN 安全 - OAuth2 刷新令牌 - 如何包含刷新令牌的到期时间
OWIN Security - OAuth2 Refresh Token - How to include Refresh Token's expiration
按照 this 指南后,我有一个功能授权服务器。我的应用程序收到以下响应:
{
"access_token": "wNl5VT4UuMwMpFOkoMTUscO7XgS96ktzeE_FoAcKpugLD4VrZGZ0HgGvgfgbY1axOPsdxQ5bzB2hA5jKtWNZdq21OvKU4LLnRXXhSHbOWLnbVSAVfkrX1n_Vv_TgWncOheK3WJ7OkELoLUkwYYQCzX712BVmblLkSjsjpvX94VywnUv16z_cnIPsUAHjHlLsEB5cFvyItGQCU2KRq-A3j70l2zBNnu9N9s3dUoidP8eiW6QpDWOnSOXKX9DG0vd6lQnUtgt7mHeP8Det55QvptRTPKABnKxB9_0QvSGa6I8",
"token_type": "bearer",
"expires_in": 1799,
"refresh_token": "a39bca5a33ef4c9fb77c3652f17152db",
"as:client_id": "myClient",
"userName": "myUser",
".issued": "Wed, 22 Feb 2017 17:05:24 GMT",
".expires": "Wed, 22 Feb 2017 17:35:24 GMT"
}
我的问题是不包括刷新令牌的到期时间。我知道我可以将此作为异常捕获,但如果可能的话,最好先避免这种情况。当在 OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials(...)
和 OAuthAuthorizationServerProvider.TokenEndpoint(...)
方法之后的 IAuthenticationTokenProvider.CreateAsync(...)
方法中创建刷新令牌时,如何包含过期 date/time。
授权提供商
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
...
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime");
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin") ?? "*";
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
using (AuthRepository repo = new AuthRepository())
{
IdentityUser user = await repo.FindUser(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"as:client_id", context.ClientId ?? string.Empty
},
{
"userName", context.UserName
}
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
}
刷新令牌提供程序
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var clientid = context.Ticket.Properties.Dictionary["as:client_id"];
if (string.IsNullOrEmpty(clientid))
{
return;
}
var refreshTokenId = Guid.NewGuid().ToString("n");
using (AuthRepository repo = new AuthRepository())
{
var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime");
var token = new RefreshToken
{
Id = Helper.GetHash(refreshTokenId),
ClientId = clientid,
Subject = context.Ticket.Identity.Name,
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime))
};
context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
context.Ticket.Properties.ExpiresUtc = token.ExpiresUtc;
token.ProtectedTicket = context.SerializeTicket();
var result = await repo.AddRefreshToken(token);
if (result)
{
context.SetToken(refreshTokenId);
}
}
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
...
}
}
我为要序列化并返回的刷新令牌创建了一个 DTO class 来代替 refreshTokenId
变量。
public class RefreshTokenDTO
{
[JsonProperty("token")]
public string Token { get; set; }
[JsonProperty("issued")]
public DateTime Issued { get; set; }
[JsonProperty("expires")]
public DateTime Expires { get; set; }
}
并更改了 SimpleRefreshTokenProvider.CreateAsync
方法
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
...
using (AuthRepository repo = new AuthRepository())
{
...
if (result)
{
//context.SetToken(refreshTokenId);
var rToken = new RefreshTokenDTO
{
Token = refreshTokenId,
Issued = token.IssuedUtc,
Expires = token.ExpiresUtc
};
var json = JsonConvert.SerializeObject(rToken);
context.SetToken(json);
}
}
}
新的回复是
{
"access_token": "dRJiWTT03KlapjqENhDeIa-f35rE4eRDn6DL60laVeKysUQkOHE2Zu6ySYYFmq53jN5KQL3A7Aj6obM3oe5iYtHbfeueODcMitzGEBXMph1-791v86VWgdvW4EtvIbhQnLq8Acr6K_Nt5qDQWTCD5DETjr6h0OenbZtDIQak3ycUUPEU5m1Ws3b2qZbw62-DSUzaOZ2TYhvdkRdFg_zWhLIDd9vIWiGXcWxTr415P5a7d1s_K-8vmq5q-I5nEUCmJshmCWIU_4oPKz7sQLHhy79JE9Z00BfdidzFbYaA9yo",
"token_type": "bearer",
"expires_in": 1799,
"refresh_token": "{\"Token\":\"f94f5ba1494644e388f0ec4862a81909\",\"Issued\":\"2017-02-22T17:48:03.771661Z\",\"Expires\":\"2017-03-04T17:48:03.771661Z\"}",
"as:client_id": "epAndroid",
"as:issued": "",
"userName": "pm00115905",
".issued": "Wed, 22 Feb 2017 17:48:02 GMT",
".expires": "Wed, 22 Feb 2017 18:18:02 GMT"
}
并且在客户端我创建了一个 JsonConverter
public class RefreshTokenJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return JsonConvert.DeserializeObject<RefreshToken>(reader.Value.ToString());
}
public override bool CanConvert(Type objectType)
{
return true;
}
}
按照 this 指南后,我有一个功能授权服务器。我的应用程序收到以下响应:
{
"access_token": "wNl5VT4UuMwMpFOkoMTUscO7XgS96ktzeE_FoAcKpugLD4VrZGZ0HgGvgfgbY1axOPsdxQ5bzB2hA5jKtWNZdq21OvKU4LLnRXXhSHbOWLnbVSAVfkrX1n_Vv_TgWncOheK3WJ7OkELoLUkwYYQCzX712BVmblLkSjsjpvX94VywnUv16z_cnIPsUAHjHlLsEB5cFvyItGQCU2KRq-A3j70l2zBNnu9N9s3dUoidP8eiW6QpDWOnSOXKX9DG0vd6lQnUtgt7mHeP8Det55QvptRTPKABnKxB9_0QvSGa6I8",
"token_type": "bearer",
"expires_in": 1799,
"refresh_token": "a39bca5a33ef4c9fb77c3652f17152db",
"as:client_id": "myClient",
"userName": "myUser",
".issued": "Wed, 22 Feb 2017 17:05:24 GMT",
".expires": "Wed, 22 Feb 2017 17:35:24 GMT"
}
我的问题是不包括刷新令牌的到期时间。我知道我可以将此作为异常捕获,但如果可能的话,最好先避免这种情况。当在 OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials(...)
和 OAuthAuthorizationServerProvider.TokenEndpoint(...)
方法之后的 IAuthenticationTokenProvider.CreateAsync(...)
方法中创建刷新令牌时,如何包含过期 date/time。
授权提供商
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
...
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime");
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin") ?? "*";
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
using (AuthRepository repo = new AuthRepository())
{
IdentityUser user = await repo.FindUser(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"as:client_id", context.ClientId ?? string.Empty
},
{
"userName", context.UserName
}
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
}
刷新令牌提供程序
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var clientid = context.Ticket.Properties.Dictionary["as:client_id"];
if (string.IsNullOrEmpty(clientid))
{
return;
}
var refreshTokenId = Guid.NewGuid().ToString("n");
using (AuthRepository repo = new AuthRepository())
{
var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime");
var token = new RefreshToken
{
Id = Helper.GetHash(refreshTokenId),
ClientId = clientid,
Subject = context.Ticket.Identity.Name,
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime))
};
context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
context.Ticket.Properties.ExpiresUtc = token.ExpiresUtc;
token.ProtectedTicket = context.SerializeTicket();
var result = await repo.AddRefreshToken(token);
if (result)
{
context.SetToken(refreshTokenId);
}
}
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
...
}
}
我为要序列化并返回的刷新令牌创建了一个 DTO class 来代替 refreshTokenId
变量。
public class RefreshTokenDTO
{
[JsonProperty("token")]
public string Token { get; set; }
[JsonProperty("issued")]
public DateTime Issued { get; set; }
[JsonProperty("expires")]
public DateTime Expires { get; set; }
}
并更改了 SimpleRefreshTokenProvider.CreateAsync
方法
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
...
using (AuthRepository repo = new AuthRepository())
{
...
if (result)
{
//context.SetToken(refreshTokenId);
var rToken = new RefreshTokenDTO
{
Token = refreshTokenId,
Issued = token.IssuedUtc,
Expires = token.ExpiresUtc
};
var json = JsonConvert.SerializeObject(rToken);
context.SetToken(json);
}
}
}
新的回复是
{
"access_token": "dRJiWTT03KlapjqENhDeIa-f35rE4eRDn6DL60laVeKysUQkOHE2Zu6ySYYFmq53jN5KQL3A7Aj6obM3oe5iYtHbfeueODcMitzGEBXMph1-791v86VWgdvW4EtvIbhQnLq8Acr6K_Nt5qDQWTCD5DETjr6h0OenbZtDIQak3ycUUPEU5m1Ws3b2qZbw62-DSUzaOZ2TYhvdkRdFg_zWhLIDd9vIWiGXcWxTr415P5a7d1s_K-8vmq5q-I5nEUCmJshmCWIU_4oPKz7sQLHhy79JE9Z00BfdidzFbYaA9yo",
"token_type": "bearer",
"expires_in": 1799,
"refresh_token": "{\"Token\":\"f94f5ba1494644e388f0ec4862a81909\",\"Issued\":\"2017-02-22T17:48:03.771661Z\",\"Expires\":\"2017-03-04T17:48:03.771661Z\"}",
"as:client_id": "epAndroid",
"as:issued": "",
"userName": "pm00115905",
".issued": "Wed, 22 Feb 2017 17:48:02 GMT",
".expires": "Wed, 22 Feb 2017 18:18:02 GMT"
}
并且在客户端我创建了一个 JsonConverter
public class RefreshTokenJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return JsonConvert.DeserializeObject<RefreshToken>(reader.Value.ToString());
}
public override bool CanConvert(Type objectType)
{
return true;
}
}