امن سازی برنامههای ASP.NET Core توسط IdentityServer 4x - قسمت نهم- مدیریت طول عمر توکنها
نویسنده: وحید نصیری
تاریخ: ۱۳۹۷/۰۶/۱۹ ۱۵:۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
namespace DNT.IDP
{
public static class Config
{
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientName = "Image Gallery",
// IdentityTokenLifetime = ... // defaults to 300 seconds / 5 minutes
// AuthorizationCodeLifetime = ... // defaults to 300 seconds / 5 minutes
// AccessTokenLifetime = ... // defaults to 3600 seconds / 1 hour
}
};
}
}
} namespace DNT.IDP
{
public static class Config
{
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientName = "Image Gallery",
// IdentityTokenLifetime = ... // defaults to 300 seconds / 5 minutes
// AuthorizationCodeLifetime = ... // defaults to 300 seconds / 5 minutes
// AccessTokenLifetime = ... // defaults to 3600 seconds / 1 hour
AllowOfflineAccess = true,
// AbsoluteRefreshTokenLifetime = ... // Defaults to 2592000 seconds / 30 days
// RefreshTokenExpiration = TokenExpiration.Sliding
UpdateAccessTokenClaimsOnRefresh = true,
// ...
}
};
}
}
} options.Scope.Add("offline_access"); using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using IdentityModel.Client;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
namespace ImageGallery.MvcClient.Services
{
public interface IImageGalleryHttpClient
{
Task<HttpClient> GetHttpClientAsync();
}
/// <summary>
/// A typed HttpClient.
/// </summary>
public class ImageGalleryHttpClient : IImageGalleryHttpClient
{
private readonly HttpClient _httpClient;
private readonly IConfiguration _configuration;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<ImageGalleryHttpClient> _logger;
public ImageGalleryHttpClient(
HttpClient httpClient,
IConfiguration configuration,
IHttpContextAccessor httpContextAccessor,
ILogger<ImageGalleryHttpClient> logger)
{
_httpClient = httpClient;
_configuration = configuration;
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
public async Task<HttpClient> GetHttpClientAsync()
{
var accessToken = string.Empty;
var currentContext = _httpContextAccessor.HttpContext;
var expires_at = await currentContext.GetTokenAsync("expires_at");
if (string.IsNullOrWhiteSpace(expires_at)
|| ((DateTime.Parse(expires_at).AddSeconds(-60)).ToUniversalTime() < DateTime.UtcNow))
{
accessToken = await RenewTokens();
}
else
{
accessToken = await currentContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
}
if (!string.IsNullOrWhiteSpace(accessToken))
{
_logger.LogInformation($"Using Access Token: {accessToken}");
_httpClient.SetBearerToken(accessToken);
}
_httpClient.BaseAddress = new Uri(_configuration["WebApiBaseAddress"]);
_httpClient.DefaultRequestHeaders.Accept.Clear();
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return _httpClient;
}
private async Task<string> RenewTokens()
{
// get the current HttpContext to access the tokens
var currentContext = _httpContextAccessor.HttpContext;
// get the metadata
var discoveryClient = new DiscoveryClient(_configuration["IDPBaseAddress"]);
var metaDataResponse = await discoveryClient.GetAsync();
// create a new token client to get new tokens
var tokenClient = new TokenClient(
metaDataResponse.TokenEndpoint,
_configuration["ClientId"],
_configuration["ClientSecret"]);
// get the saved refresh token
var currentRefreshToken = await currentContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
// refresh the tokens
var tokenResult = await tokenClient.RequestRefreshTokenAsync(currentRefreshToken);
if (tokenResult.IsError)
{
throw new Exception("Problem encountered while refreshing tokens.", tokenResult.Exception);
}
// update the tokens & expiration value
var updatedTokens = new List<AuthenticationToken>();
updatedTokens.Add(new AuthenticationToken
{
Name = OpenIdConnectParameterNames.IdToken,
Value = tokenResult.IdentityToken
});
updatedTokens.Add(new AuthenticationToken
{
Name = OpenIdConnectParameterNames.AccessToken,
Value = tokenResult.AccessToken
});
updatedTokens.Add(new AuthenticationToken
{
Name = OpenIdConnectParameterNames.RefreshToken,
Value = tokenResult.RefreshToken
});
var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
updatedTokens.Add(new AuthenticationToken
{
Name = "expires_at",
Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
});
// get authenticate result, containing the current principal & properties
var currentAuthenticateResult = await currentContext.AuthenticateAsync("Cookies");
// store the updated tokens
currentAuthenticateResult.Properties.StoreTokens(updatedTokens);
// sign in
await currentContext.SignInAsync("Cookies",
currentAuthenticateResult.Principal, currentAuthenticateResult.Properties);
// return the new access token
return tokenResult.AccessToken;
}
}
} namespace DNT.IDP
{
public static class Config
{
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientName = "Image Gallery",
// ...
AccessTokenType = AccessTokenType.Reference
}
};
}
}
} namespace DNT.IDP
{
public static class Config
{
// api-related resources (scopes)
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource(
name: "imagegalleryapi",
displayName: "Image Gallery API",
claimTypes: new List<string> {"role" })
{
ApiSecrets = { new Secret("apisecret".Sha256()) }
}
};
} namespace ImageGallery.WebApi.WebApp
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(defaultScheme: IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = Configuration["IDPBaseAddress"];
options.ApiName = "imagegalleryapi";
options.ApiSecret = "apisecret";
}); namespace ImageGallery.MvcClient.WebApp.Controllers
{
[Authorize]
public class GalleryController : Controller
{
public async Task Logout()
{
await revokeTokens();
// Clears the local cookie ("Cookies" must match the name of the scheme)
await HttpContext.SignOutAsync("Cookies");
await HttpContext.SignOutAsync("oidc");
}
private async Task revokeTokens()
{
var discoveryClient = new DiscoveryClient(_configuration["IDPBaseAddress"]);
var metaDataResponse = await discoveryClient.GetAsync();
var tokenRevocationClient = new TokenRevocationClient(
metaDataResponse.RevocationEndpoint,
_configuration["ClientId"],
_configuration["ClientSecret"]
);
var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
if (!string.IsNullOrWhiteSpace(accessToken))
{
var response = await tokenRevocationClient.RevokeAccessTokenAsync(accessToken);
if (response.IsError)
{
throw new Exception("Problem accessing the TokenRevocation endpoint.", response.Exception);
}
}
var refreshToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
if (!string.IsNullOrWhiteSpace(refreshToken))
{
var response = await tokenRevocationClient.RevokeRefreshTokenAsync(refreshToken);
if (response.IsError)
{
throw new Exception("Problem accessing the TokenRevocation endpoint.", response.Exception);
}
}
}
private async Task<string> RenewTokens()
{
// get the current HttpContext to access the tokens
var currentContext = _httpContextAccessor.HttpContext;
var disco = await _httpClient.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest
{
Address = _configuration["IdentityServer:IDPBaseAddress"]
});
if (disco.IsError) throw new Exception(disco.Error);
// get the saved refresh token
var currentRefreshToken = await currentContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
// refresh the tokens
var response = await _httpClient.RequestRefreshTokenAsync(new RefreshTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = _configuration["IdentityServer:ClientId"],
ClientSecret = _configuration["IdentityServer:ClientSecret"],
RefreshToken = currentRefreshToken
});
if (response.IsError)
{
throw new Exception("Problem encountered while refreshing tokens.", response.Exception);
}
// update the tokens & expiration value
var updatedTokens = new List<AuthenticationToken>();
updatedTokens.Add(new AuthenticationToken
{
Name = OpenIdConnectParameterNames.IdToken,
Value = response.IdentityToken
});
updatedTokens.Add(new AuthenticationToken
{
Name = OpenIdConnectParameterNames.AccessToken,
Value = response.AccessToken
});
updatedTokens.Add(new AuthenticationToken
{
Name = OpenIdConnectParameterNames.RefreshToken,
Value = response.RefreshToken
});
var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(response.ExpiresIn);
updatedTokens.Add(new AuthenticationToken
{
Name = "expires_at",
Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
});
// get authenticate result, containing the current principal & properties
var currentAuthenticateResult = await currentContext.AuthenticateAsync("Cookies");
// store the updated tokens
currentAuthenticateResult.Properties.StoreTokens(updatedTokens);
// sign in
await currentContext.SignInAsync("Cookies",
currentAuthenticateResult.Principal, currentAuthenticateResult.Properties);
// return the new access token
return response.AccessToken;
} //...
.AddIdentityServerAuthentication(options =>
{
options.JwtValidationClockSkew = TimeSpan.Zero; چگونه میتوان sign out را پیاده سازی کرد