Skip to content

Commit

Permalink
Auto attach authorization header by implementing AuthenticatedDelegat…
Browse files Browse the repository at this point in the history
…ingHandler; Use IHttpClientFactory to create HttpClient in AuthenticatedServiceBase;
  • Loading branch information
Leon Liu committed Oct 11, 2021
1 parent be5b724 commit 2d31b63
Show file tree
Hide file tree
Showing 11 changed files with 91 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="6.0.0-rc.1.21452.15" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0-rc.1.21451.13" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0-rc.1.21451.13" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0-rc.1.21451.13" />
</ItemGroup>

<ItemGroup>
<Using Include="Microsoft.Extensions.Logging"/>
<Using Include="Microsoft.Extensions.Options"/>
<Using Include="Microsoft.Extensions.Logging" />
<Using Include="Microsoft.Extensions.Options" />
</ItemGroup>

</Project>

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Net.Http.Headers;
using HackSystem.Web.Authentication.Options;
using HackSystem.Web.Authentication.TokenHandlers;

namespace HackSystem.Web.Authentication.WebServices;

public class AuthenticatedDelegatingHandler : DelegatingHandler
{
private readonly IHackSystemAuthenticationTokenHandler authenticationTokenHandler;
private readonly HackSystemAuthenticationOptions options;

public AuthenticatedDelegatingHandler(
IHackSystemAuthenticationTokenHandler authenticationTokenHandler,
IOptionsSnapshot<HackSystemAuthenticationOptions> optionsSnapshot)
{
this.authenticationTokenHandler = authenticationTokenHandler;
this.options = optionsSnapshot.Value;
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
await this.AttachAuthorizationHeader(request);
return await base.SendAsync(request, cancellationToken);
}

protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)
{
this.AttachAuthorizationHeader(request).ConfigureAwait(false);
return base.Send(request, cancellationToken);
}

protected async Task AttachAuthorizationHeader(HttpRequestMessage request)
{
if (request.Headers.Authorization is null)
{
var token = await this.authenticationTokenHandler.GetTokenAsync();
request.Headers.Authorization = new AuthenticationHeaderValue(this.options.AuthenticationScheme, token);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@

public class AuthenticatedServiceBase
{
public const string AuthenticatedClientName = "AuthenticatedClient";
private readonly IHttpClientFactory httpClientFactory;
protected readonly ILogger<AuthenticatedServiceBase> logger;
protected readonly AuthenticatedHttpClient httpClient;

protected HttpClient HttpClient { get; init; }

public AuthenticatedServiceBase(
ILogger<AuthenticatedServiceBase> logger,
AuthenticatedHttpClient httpClient)
IHttpClientFactory httpClientFactory)
{
this.logger = logger;
this.httpClient = httpClient;
this.httpClientFactory = httpClientFactory;
this.HttpClient = this.httpClientFactory.CreateClient(AuthenticatedClientName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@ public static IServiceCollection AddHackSystemAuthentication(
.AddScoped<AuthenticationStateProvider, HackSystemAuthenticationStateProvider>()
.AddScoped<IHackSystemAuthenticationStateUpdater, HackSystemAuthenticationStateUpdater>()
.AddSingleton<IHackSystemAuthenticationTokenRefresher, HackSystemAuthenticationTokenRefresher>()
.AddTransient<AuthenticatedHttpClient>();
.AddTransient<AuthenticatedDelegatingHandler>()
.AddHttpClient(AuthenticatedServiceBase.AuthenticatedClientName)
.ConfigureHttpClient((sp, client) =>
{
var options = sp.GetRequiredService<IOptions<HackSystemAuthenticationOptions>>().Value;
client.BaseAddress = new Uri(options.AuthenticationURL);
})
.AddHttpMessageHandler<AuthenticatedDelegatingHandler>();

return services;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,28 @@ namespace HackSystem.Web.Authentication.TokenHandlers;

public class HackSystemAuthenticationTokenRefresher : IHackSystemAuthenticationTokenRefresher
{
private readonly ILogger<HackSystemAuthenticationTokenRefresher> logger;
private readonly IHackSystemAuthenticationStateUpdater hackSystemAuthenticationStateHandler;
private readonly int period;
private readonly Timer timer;
private readonly HttpClient httpClient;
private readonly IHttpClientFactory httpClientFactory;
private readonly ILogger<HackSystemAuthenticationTokenRefresher> logger;
private readonly IOptionsMonitor<HackSystemAuthenticationOptions> options;
private readonly AuthenticatedHttpClient httpClient;
private readonly int period;
private readonly IHackSystemAuthenticationStateUpdater hackSystemAuthenticationStateHandler;

public HackSystemAuthenticationTokenRefresher(
ILogger<HackSystemAuthenticationTokenRefresher> logger,
IOptionsMonitor<HackSystemAuthenticationOptions> options,
IServiceScopeFactory serviceScopeFactory,
IOptionsMonitor<HackSystemAuthenticationOptions> options)
IHttpClientFactory httpClientFactory)
{
this.logger = logger;
this.options = options;
this.httpClientFactory = httpClientFactory;
this.period = options.CurrentValue.TokenRefreshInMinutes * 1000 * 60;
this.timer = new Timer(new TimerCallback(this.RefreshTokenCallBack), null, Timeout.Infinite, period);

var provider = serviceScopeFactory.CreateScope().ServiceProvider;
this.httpClient = provider.GetRequiredService<AuthenticatedHttpClient>();
this.httpClient = httpClientFactory.CreateClient(AuthenticatedServiceBase.AuthenticatedClientName);
this.hackSystemAuthenticationStateHandler = provider.GetService<AuthenticationStateProvider>() as IHackSystemAuthenticationStateUpdater;
}

Expand All @@ -36,7 +39,7 @@ public void StartRefresher()
this.IsRunning = true;

// Set first paramter as 0 to invoke once immediately.
this.timer.Change(60 * 1000, period);
this.timer.Change(5 * 1000, period);
}

public void StopRefresher()
Expand All @@ -53,7 +56,6 @@ protected void RefreshTokenCallBack(object state)
public virtual async Task<string> RefreshTokenAsync()
{
this.logger.LogInformation($"Hack System refresh Token ...");
await httpClient.AddAuthorizationHeaderAsync();
var response = await httpClient.GetAsync("api/token/refresh");
var content = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,14 @@ namespace HackSystem.Web.Infrastructure.Authentication;

public class AuthenticationService : AuthenticatedServiceBase, IAuthenticationService
{
private readonly ILogger<AuthenticationService> logger;
private readonly AuthenticatedHttpClient httpClient;
private readonly IHackSystemAuthenticationStateUpdater authenticationStateUpdater;

public AuthenticationService(
ILogger<AuthenticationService> logger,
AuthenticatedHttpClient httpClient,
IHttpClientFactory httpClientFactory,
IHackSystemAuthenticationStateUpdater authenticationStateUpdater)
: base(logger, httpClient)
: base(logger, httpClientFactory)
{
this.logger = logger;
this.httpClient = httpClient;
this.authenticationStateUpdater = authenticationStateUpdater;
}

Expand All @@ -32,7 +28,7 @@ public AuthenticationService(
public async Task<RegisterResponse> Register(RegisterRequest register)
{
logger.LogDebug($"Register new user: {register.UserName}");
var response = await httpClient.PostAsJsonAsync("api/accounts/register", register);
var response = await this.HttpClient.PostAsJsonAsync("api/accounts/register", register);
var registerResult = JsonConvert.DeserializeObject<RegisterResponse>(await response.Content.ReadAsStringAsync());
return registerResult;
}
Expand All @@ -45,7 +41,7 @@ public async Task<RegisterResponse> Register(RegisterRequest register)
public async Task<LoginResponse> Login(LoginRequest login)
{
logger.LogDebug($"Login user: {login.UserName}");
var response = await httpClient.PostAsJsonAsync("api/accounts/login", login);
var response = await this.HttpClient.PostAsJsonAsync("api/accounts/login", login);
var loginResult = JsonConvert.DeserializeObject<LoginResponse>(await response.Content.ReadAsStringAsync());
if (!response.IsSuccessStatusCode)
{
Expand All @@ -63,8 +59,7 @@ public async Task<LoginResponse> Login(LoginRequest login)
public async Task<string> GetAccountInfo()
{
logger.LogDebug($"Get account information...");
await httpClient.AddAuthorizationHeaderAsync();
var response = await httpClient.GetAsync("api/accounts/GetAccountInfo");
var response = await HttpClient.GetAsync("api/accounts/GetAccountInfo");
if (!response.IsSuccessStatusCode)
{
return $"{(int)response.StatusCode} - {response.StatusCode}";
Expand All @@ -84,8 +79,7 @@ public async Task Logout()

try
{
await httpClient.AddAuthorizationHeaderAsync();
await httpClient.GetAsync("api/accounts/logout");
await this.HttpClient.GetAsync("api/accounts/logout");
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,20 @@ public class ProgramAssetService : AuthenticatedServiceBase, IProgramAssetServic
{
public ProgramAssetService(
ILogger<ProgramAssetService> logger,
AuthenticatedHttpClient httpClient)
: base(logger, httpClient)
IHttpClientFactory httpClientFactory)
: base(logger, httpClientFactory)
{
}

public async Task<ProgramAssetPackageResponse> QueryProgramAssetList(string programId)
{
await httpClient.AddAuthorizationHeaderAsync();
var result = await this.httpClient.GetFromJsonAsync<ProgramAssetPackageResponse>($"api/programasset/QueryProgramAssetList?programId={programId}");
var result = await this.HttpClient.GetFromJsonAsync<ProgramAssetPackageResponse>($"api/programasset/QueryProgramAssetList?programId={programId}");
return result;
}

public async Task<ProgramAssetPackageResponse> QueryProgramAssetPackage(ProgramAssetPackageRequest packageRequest)
{
var response = await this.httpClient.PostAsJsonAsync("api/programasset/QueryProgramAssetPackage", packageRequest);
var response = await this.HttpClient.PostAsJsonAsync("api/programasset/QueryProgramAssetPackage", packageRequest);
if (!response.IsSuccessStatusCode)
{
throw new InvalidOperationException($"Invalid operation exception ({response.StatusCode}) when requesting program asset package of program {packageRequest.ProgramId}.");
Expand Down
17 changes: 8 additions & 9 deletions HackSystem.Web.Infrastructure/Program/ProgramDetailService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,48 @@ public class ProgramDetailService : AuthenticatedServiceBase, IProgramDetailServ
{
public ProgramDetailService(
ILogger<ProgramDetailService> logger,
AuthenticatedHttpClient httpClient)
: base(logger, httpClient)
IHttpClientFactory httpClientFactory)
: base(logger, httpClientFactory)
{
}

public async Task<IEnumerable<UserProgramMapResponse>> QueryUserProgramMaps()
{
await httpClient.AddAuthorizationHeaderAsync();
var result = await this.httpClient.GetFromJsonAsync<IEnumerable<UserProgramMapResponse>>("api/ProgramDetail/QueryUserProgramMaps");
var result = await this.HttpClient.GetFromJsonAsync<IEnumerable<UserProgramMapResponse>>("api/ProgramDetail/QueryUserProgramMaps");
return result;
}

public async Task<bool> SetUserProgramHide(UserProgramMapRequest hideRequest)
{
var response = await this.httpClient.PutAsJsonAsync("api/ProgramDetail/SetUserProgramHide", hideRequest);
var response = await this.HttpClient.PutAsJsonAsync("api/ProgramDetail/SetUserProgramHide", hideRequest);
response.EnsureSuccessStatusCode();
return response.IsSuccessStatusCode;
}

public async Task<bool> SetUserProgramPinToDock(UserProgramMapRequest pinToDockRequest)
{
var response = await this.httpClient.PutAsJsonAsync("api/ProgramDetail/SetUserProgramPinToDock", pinToDockRequest);
var response = await this.HttpClient.PutAsJsonAsync("api/ProgramDetail/SetUserProgramPinToDock", pinToDockRequest);
response.EnsureSuccessStatusCode();
return response.IsSuccessStatusCode;
}

public async Task<bool> SetUserProgramPinToTop(UserProgramMapRequest pinToTopRequest)
{
var response = await this.httpClient.PutAsJsonAsync("api/ProgramDetail/SetUserProgramPinToTop", pinToTopRequest);
var response = await this.HttpClient.PutAsJsonAsync("api/ProgramDetail/SetUserProgramPinToTop", pinToTopRequest);
response.EnsureSuccessStatusCode();
return response.IsSuccessStatusCode;
}

public async Task<bool> SetUserProgramRename(UserProgramMapRequest renameRequest)
{
var response = await this.httpClient.PutAsJsonAsync("api/ProgramDetail/SetUserProgramRename", renameRequest);
var response = await this.HttpClient.PutAsJsonAsync("api/ProgramDetail/SetUserProgramRename", renameRequest);
response.EnsureSuccessStatusCode();
return response.IsSuccessStatusCode;
}

public async Task<bool> DeleteUserProgramMap(string programId)
{
var response = await this.httpClient.DeleteAsync($"api/ProgramDetail/DeleteUserProgramMap?programId={programId}");
var response = await this.HttpClient.DeleteAsync($"api/ProgramDetail/DeleteUserProgramMap?programId={programId}");
response.EnsureSuccessStatusCode();
return response.IsSuccessStatusCode;
}
Expand Down
9 changes: 4 additions & 5 deletions HackSystem.Web.TaskSchedule/Services/TaskDetailService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,20 @@ public class TaskDetailService : AuthenticatedServiceBase, ITaskDetailService
{
public TaskDetailService(
ILogger<TaskDetailService> logger,
AuthenticatedHttpClient httpClient)
: base(logger, httpClient)
IHttpClientFactory httpClientFactory)
: base(logger, httpClientFactory)
{
}

public async Task<bool> ExecuteTask(TaskDetailRequest taskDetail)
{
var result = await this.httpClient.PostAsJsonAsync("api/taskserver/ExecuteTask", taskDetail);
var result = await this.HttpClient.PostAsJsonAsync("api/taskserver/ExecuteTask", taskDetail);
return result.IsSuccessStatusCode;
}

public async Task<IEnumerable<TaskDetailResponse>> QueryTasks()
{
await httpClient.AddAuthorizationHeaderAsync();
var result = await this.httpClient.GetFromJsonAsync<IEnumerable<TaskDetailResponse>>("api/taskserver/QueryTasks");
var result = await this.HttpClient.GetFromJsonAsync<IEnumerable<TaskDetailResponse>>("api/taskserver/QueryTasks");
return result;
}
}
3 changes: 1 addition & 2 deletions HackSystem.Web.TaskSchedule/TaskSchedulerComponent.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using HackSystem.DataTransferObjects.TaskServer;
using HackSystem.Web.Authentication.WebServices;
using HackSystem.Web.Component.ToastContainer;
using HackSystem.Web.TaskSchedule.Services;

Expand All @@ -21,7 +20,7 @@ protected override void OnInitialized()
this.serviceScope = this.ServiceScopeFactory.CreateScope();
this.taskDetailService = new TaskDetailService(
this.serviceScope.ServiceProvider.GetRequiredService<ILogger<TaskDetailService>>(),
this.serviceScope.ServiceProvider.GetRequiredService<AuthenticatedHttpClient>());
this.serviceScope.ServiceProvider.GetRequiredService<IHttpClientFactory>());
}

protected override async Task OnAfterRenderAsync(bool firstRender)
Expand Down

0 comments on commit 2d31b63

Please sign in to comment.