#identityserver4 #access-token #dotnet-httpclient #blazor-server-side #httpclientfactory
Вопрос:
В настоящее время у меня есть серверное приложение blazor, identityserver4 и API. В настоящее время поток кода работает просто отлично. Текущая проблема заключается в том, что класс клиента службы, используемый в приложении blazor, должен устанавливать маркер доступа в заголовках авторизации, полученных от IdentityServer4, для каждого вызова API. Это утомительно/повторять. Каков наилучший способ использовать HttpClientFactory и реализовать его, чтобы мне не приходилось беспокоиться о настройке токена на предъявителя для каждого вызова API?
Следуя некоторым документам, в настоящее время у меня есть классы ниже. Оба добавлены как службы с областью действия. 1.Поставщик токенов 2.Менеджер токенов
public class TokenManager
{
private readonly TokenProvider _tokenProvider;
private readonly IHttpClientFactory _httpClientFactory;
public TokenManager(TokenProvider tokenProvider,
IHttpClientFactory httpClientFactory)
{
_tokenProvider = tokenProvider ??
throw new ArgumentNullException(nameof(tokenProvider));
_httpClientFactory = httpClientFactory ??
throw new ArgumentNullException(nameof(httpClientFactory));
}
public async Task<string> RetrieveAccessTokenFromIdentityProvider()
{
if ((_tokenProvider.ExpiresAt.AddSeconds(-60)).ToUniversalTime()
> DateTime.UtcNow)
{
return _tokenProvider.AccessToken;
}
var idpClient = _httpClientFactory.CreateClient();
var discoveryReponse = await idpClient
.GetDiscoveryDocumentAsync("https://localhost:44359/");
var refreshResponse = await idpClient.RequestRefreshTokenAsync(
new RefreshTokenRequest
{
Address = discoveryReponse.TokenEndpoint,
ClientId = "someclient",
ClientSecret = "supersecret",
RefreshToken = _tokenProvider.RefreshToken
});
_tokenProvider.AccessToken = refreshResponse.AccessToken;
_tokenProvider.RefreshToken = refreshResponse.RefreshToken;
_tokenProvider.ExpiresAt = DateTime.UtcNow.AddSeconds(refreshResponse.ExpiresIn);
return _tokenProvider.AccessToken;
}
}
public class TokenProvider
{
public TokenProvider()
{
}
public string XsrfToken { get; set; }
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
public DateTimeOffset ExpiresAt { get; set; }
}
Приложение.бритва:
@inject TokenProvider TokenProvider
@code{
[Parameter]
public InitialApplicationState InitialState { get; set; }
protected override Task OnInitializedAsync()
{
TokenProvider.XsrfToken = InitialState.XsrfToken;
TokenProvider.AccessToken = InitialState.AccessToken;
TokenProvider.RefreshToken = InitialState.RefreshToken;
TokenProvider.ExpiresAt = InitialState.ExpiresAt;
return base.OnInitializedAsync();
}
}
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
<h1>Sorry, unauthorized access request</h1>
<p>Try logging in...</p>
</NotAuthorized>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
_Host.cshtml:
<body>
@{
var initialTokenState = new InitialApplicationState
{
XsrfToken = Xsrf.GetAndStoreTokens(HttpContext).RequestToken,
AccessToken = await HttpContext.GetTokenAsync("access_token"),
RefreshToken = await HttpContext.GetTokenAsync("refresh_token")
};
var expiresAt = await HttpContext.GetTokenAsync("expires_at");
if (DateTimeOffset.TryParse(expiresAt,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out var expiration))
{
initialTokenState.ExpiresAt = expiration;
}
else
{
initialTokenState.ExpiresAt = DateTimeOffset.UtcNow;
}
}
<app>
<component type="typeof(App)" render-mode="ServerPrerendered"
param-InitialState="initialTokenState" />
</app>
<div id="blazor-error-ui">
<environment include="Staging,Production">
An error has occurred. This application may no longer respond until reloaded.
</environment>
<environment include="Development">
An unhandled exception has occurred. See browser dev tools for details.
</environment>
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.server.js"></script>
</body>
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<IReportService, ReportService>(client =>
{
client.BaseAddress = new Uri("https://localhost:44340/");
});
services.AddSingleton<WeatherForecastService>();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme,
options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "https://localhost:44359/";
options.ClientId = "someclient";
options.ClientSecret = "supersecret";
options.CallbackPath = "/signin-oidc";
options.ResponseType = "code";
options.ResponseMode = "form_post";
options.UsePkce = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("phone");
options.Scope.Add("someclient-api");
options.Scope.Add("offline_access");
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.TokenValidationParameters.NameClaimType = "given_name";
});
services.AddScoped<TokenProvider>();
services.AddScoped<TokenManager>();
}
ServiceClient.cs The line of code that sets the Bearer token concerns me. How to avoid writing this line for every call to API?
public async Task<IEnumerable<Report>> GetReportList(DefaultDates dates)
{
*httpClient.SetBearerToken(await tokenManager.RetrieveAccessTokenFromIdentityProvider());*
var response = await httpClient.PostAsJsonAsync($"api/Reports/GetReport", dates);
if (response.IsSuccessStatusCode)
{
return await response.ReadContentAs<List<Report>>();
}
return null;
}