问题描述
我目前有一个 blazor 服务器应用程序、identityserver4 和一个 API。目前代码流工作得很好。当前的问题是 blazor 应用程序中使用的服务客户端类必须在从 IdentityServer4 接收到每个 API 调用的授权标头中设置访问令牌。这是乏味/重复的事情。使用 HttpClientFactory 并实现它的最佳方法是什么,这样我就不必担心为每个 API 调用设置承载令牌?
根据一些文档,目前我有以下课程。两者都作为范围服务添加。 1.TokenProvider 2.TokenManager
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; }
}
App.razor:
@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 设置不记名令牌的代码行让我很担心。如何避免每次调用 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;
}
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)