Я сделал промежуточное ПО, которое выполняет эту работу автоматически, когда прошло больше половины жизни токена доступа. Таким образом, вам не нужно вызывать какой-либо метод или применять какой-либо фильтр. Просто вставьте это в Startup.cs, и все приложение будет покрыто:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Other code here
app.UseAutomaticSilentRenew("http://localhost:5000/", "clientId", "clientSecret")
app.UseAccessTokenLifetime();
// And here
}
UseAutomaticSilentRenew - возобновляет доступ и обновляет токены
UseAccessTokenLifetime - выдает пользователя из системы, если срок действия токена доступа истек. Поместите это после UseAutomaticSilentRenew
, чтобы он работал только в том случае, если UseAutomaticSilentRenew не удалось получить новый токен доступа ранее.
Реализация:
public static class OidcExtensions
{
public static IApplicationBuilder UseAutomaticSilentRenew(this IApplicationBuilder builder, string authority, string clientId, string clientSecret, string cookieSchemeName = CookieAuthenticationDefaults.AuthenticationScheme)
{
return builder.UseMiddleware<AutomaticSilentRenewMiddleware>(authority, clientId, clientSecret, cookieSchemeName);
}
public static IApplicationBuilder UseAccessTokenLifetime(this IApplicationBuilder builder, string cookieSchemeName = CookieAuthenticationDefaults.AuthenticationScheme)
{
return builder.UseMiddleware<TokenLifetimeMiddleware>(OpenIdConnectParameterNames.AccessToken, cookieSchemeName);
}
public static IApplicationBuilder UseIdTokenLifetime(this IApplicationBuilder builder, string cookieSchemeName = CookieAuthenticationDefaults.AuthenticationScheme)
{
return builder.UseMiddleware<TokenLifetimeMiddleware>(OpenIdConnectParameterNames.IdToken, cookieSchemeName);
}
}
public class AutomaticSilentRenewMiddleware
{
private readonly RequestDelegate next;
private readonly string authority;
private readonly string clientId;
private readonly string clientSecret;
private readonly string cookieSchemeName;
public AutomaticSilentRenewMiddleware(RequestDelegate next, string authority, string clientId, string clientSecret, string cookieSchemeName)
{
this.next = next;
this.authority = authority;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.cookieSchemeName = cookieSchemeName;
}
public async Task InvokeAsync(HttpContext context)
{
string oldAccessToken = await context.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
if (!string.IsNullOrEmpty(oldAccessToken))
{
JwtSecurityToken tokenInfo = new JwtSecurityToken(oldAccessToken);
// Renew access token if pass halfway of its lifetime
if (tokenInfo.ValidFrom + (tokenInfo.ValidTo - tokenInfo.ValidFrom) / 2 < DateTime.UtcNow)
{
string tokenEndpoint;
var disco = await DiscoveryClient.GetAsync(authority);
if (!disco.IsError)
{
tokenEndpoint = disco.TokenEndpoint;
}
else
{
// If failed to get discovery document use default URI
tokenEndpoint = authority + "/connect/token";
}
TokenClient tokenClient = new TokenClient(tokenEndpoint, clientId, clientSecret);
string oldRefreshToken = await context.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
TokenResponse tokenResult = await tokenClient.RequestRefreshTokenAsync(oldRefreshToken);
if (!tokenResult.IsError)
{
string idToken = await context.GetTokenAsync(OpenIdConnectParameterNames.IdToken);
string newAccessToken = tokenResult.AccessToken;
string newRefreshToken = tokenResult.RefreshToken;
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken { Name = OpenIdConnectParameterNames.IdToken, Value = idToken },
new AuthenticationToken { Name = OpenIdConnectParameterNames.AccessToken, Value = newAccessToken },
new AuthenticationToken { Name = OpenIdConnectParameterNames.RefreshToken, Value = newRefreshToken }
};
AuthenticateResult info = await context.AuthenticateAsync(cookieSchemeName);
info.Properties.StoreTokens(tokens);
await context.SignInAsync(cookieSchemeName, info.Principal, info.Properties);
}
}
}
await next.Invoke(context);
}
}
public class TokenLifetimeMiddleware
{
private readonly RequestDelegate next;
private readonly string tokenName;
private readonly string cookieSchemeName;
public TokenLifetimeMiddleware(RequestDelegate next, string tokenName, string cookieSchemeName)
{
this.next = next;
this.tokenName = tokenName;
this.cookieSchemeName = cookieSchemeName;
}
public async Task InvokeAsync(HttpContext context)
{
string token = await context.GetTokenAsync(tokenName);
if (!string.IsNullOrEmpty(token))
{
DateTime validTo = new JwtSecurityToken(token).ValidTo;
if (validTo < DateTime.UtcNow)
{
// Sign out if token is no longer valid
await context.SignOutAsync(cookieSchemeName);
}
}
await next.Invoke(context);
}
}
Примечание. Я не устанавливал срок действия cookie, потому что в нашем случае он зависит от времени жизни токена обновления, который не предоставляется сервером идентификации. Если бы я согласовал истечение срока действия файла cookie с истечением срока действия токена доступа, я бы не смог обновить токен доступа после его истечения.
Да, и еще кое-что. UseAccessTokenLifetime
удаляет файл cookie, но не выполняет выход пользователя. Выход происходит после перезагрузки страницы. Не нашел способа исправить это.
person
Максим Кошевой
schedule
21.09.2018