簡體   English   中英

使用Blazor使用Refresh Token實施短暫的Jwt

[英]Implementing short lived Jwt with Refresh Token with Blazor

我們目前正在開發Blazor應用,該應用使用具有刷新令牌的短暫(10分鍾)Jwt進行保護。

當前,我們已經實現了Jwt,並且通過Blazor服務器端Web api可以登錄,生成Jwt並生成刷新令牌。

在客戶端,我使用以下鏈接;

使用客戶端Blazor進行身份驗證

並如下擴展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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM