[英]Invalid JWT: Token must be a short-lived token and in a reasonable timeframe
[英]Implementing short lived Jwt with Refresh Token with Blazor
我們目前正在開發Blazor
應用,該應用使用具有刷新令牌的短暫(10分鍾)Jwt進行保護。
當前,我們已經實現了Jwt,並且通過Blazor服務器端Web api可以登錄,生成Jwt並生成刷新令牌。
在客戶端,我使用以下鏈接;
並如下擴展ApiAuthenticationStateProvider.cs
;
public class ApiAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly HttpClient _httpClient;
private readonly ILocalStorageService _localStorage;
public ApiAuthenticationStateProvider(HttpClient httpClient, ILocalStorageService localStorage)
{
_httpClient = httpClient;
_localStorage = localStorage;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var savedToken = await _localStorage.GetItemAsync<string>("authToken");
var refreshToken = await _localStorage.GetItemAsync<string>("refreshToken");
if (string.IsNullOrWhiteSpace(savedToken) || string.IsNullOrWhiteSpace(refreshToken))
{
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
}
var userResponse = await _httpClient.GetAsync<UserModel>("api/accounts/user", savedToken);
if(userResponse.HasError)
{
var response = await _httpClient.PostAsync<LoginResponse>("api/login/refreshToken", new RefreshTokenModel { RefreshToken = refreshToken });
//check result now
if (!response.HasError)
{
await _localStorage.SetItemAsync("authToken", response.Result.AccessToken);
await _localStorage.SetItemAsync("refreshToken", response.Result.RefreshToken);
userResponse = await _httpClient.GetAsync<UserModel>("api/accounts/user", response.Result.AccessToken);
}
}
var identity = !userResponse.HasError ? new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, userResponse.Result.Email) }, "apiauth") : new ClaimsIdentity();
return new AuthenticationState(new ClaimsPrincipal(identity));
}
public void MarkUserAsAuthenticated(string email)
{
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, email) }, "apiauth"));
var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
NotifyAuthenticationStateChanged(authState);
}
public void MarkUserAsLoggedOut()
{
var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity());
var authState = Task.FromResult(new AuthenticationState(anonymousUser));
NotifyAuthenticationStateChanged(authState);
}
}
因此,如果Jwt第一次失敗,我們將嘗試使用刷新令牌進行續訂。
上面的代碼正在運行,但是,如果我隨后導航到/fetchData
測試端點(受[Authorize]
屬性保護),則發現的第一個問題是。 該頁面最初運行正常,並在標頭中發送Jwt。 但是,如果我然后按f5
並刷新頁面,則在/fecthData
端點(即代碼) /fecthData
獲得401未經授權的信息;
@code {
WeatherForecast[] forecasts;
protected override async Task OnInitAsync()
{
forecasts = await Http.GetJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts");
}
}
現在,如果要解決此問題,我可以手動將Jwt表單localStorage添加到標題中(在我的情況下,我使用擴展方法);
public static async Task<ServiceResponse<T>> GetAsync<T>(
this HttpClient httpClient, string url, string token)
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
var response = await httpClient.GetAsync(url);
return await BuildResponse<T>(response);
}
但是,我這里遇到的第二個問題是,如果Jwt在此調用期間過期,則需要調用以使用刷新令牌來獲取新的Jwt。
有沒有辦法我可以使用中間件來做到這一點,從而避免每次調用時都要檢查401,然后以這種方式更新令牌?
很多時候,我們考慮將Blazor作為MVC,但事實並非如此。 它更像是在瀏覽器中運行的桌面應用程序。 我以這種方式使用JWT和更新令牌:登錄后,我遇到一個無限循環,該循環對后端執行ping操作並保持會話並更新令牌。 簡化:
class JWTAuthenticationStateProvider : AuthenticationStateProvider
{
private bool IsLogedIn = false;
private CustomCredentials credentials = null;
// private ClaimsPrincipal currentClaimsPrincipal = null; (optinally)
public Task Login( string user, string password )
{
credentials = go_backend_login_service( user, password );
// do stuff with credentials and claims
// I raise event here to notify login
keepSession( );
}
public Task Logout( )
{
go_bakcend_logout_service( credentials );
// do stuff with claims
IsLogedIn = false;
// I raise event here to notify logout
}
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
// make a response from credentials or currentClaimsPrincipal
}
private async void KeepSession()
{
while(IsLogedIn)
{
credentials = go_backend_renewingJWT_service( credentials );
// do stuff with new credentials: check are ok, update IsLogedIn, ...
// I raise event here if server says logout
await Task.Delay(1000); // sleep for a while.
}
}
}
請記住通過DI注冊組件:
public void ConfigureServices(IServiceCollection services)
{
// ... other services added here ...
// One JWTAuthenticationStateProvider for each connection on server side.
// A singleton for clientside.
services.AddScoped<AuthenticationStateProvider,
JWTAuthenticationStateProvider>();
}
這只是一個主意,您應該考慮一下並使其適應您自己的解決方案。
有關github SteveSandersonMS / blazor-auth.md上身份驗證和授權的更多信息
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.