C# 用Blazor实现具有刷新令牌的短期Jwt

C# 用Blazor实现具有刷新令牌的短期Jwt,c#,jwt,blazor,blazor-client-side,C#,Jwt,Blazor,Blazor Client Side,我们目前正在开发一款Blazor应用程序,该应用程序使用具有刷新令牌的短期(10分钟)Jwt进行保护 目前我们已经实现了Jwt,通过Blazor服务器端web api可以登录、生成Jwt和生成刷新令牌 从客户端,我使用了以下链接 并将apauthenticationstateprovider.cs扩展如下 public class ApiAuthenticationStateProvider : AuthenticationStateProvider { private readonl

我们目前正在开发一款
Blazor
应用程序,该应用程序使用具有刷新令牌的短期(10分钟)Jwt进行保护

目前我们已经实现了Jwt,通过Blazor服务器端web api可以登录、生成Jwt和生成刷新令牌

从客户端,我使用了以下链接

并将apauthenticationstateprovider.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表单localStorage添加到头中(在我的示例中,我使用扩展方法)

公共静态异步任务GetAsync(
此HttpClient(HttpClient、字符串url、字符串令牌)
{
httpClient.DefaultRequestHeaders.Authorization=新的AuthenticationHeaderValue(“承载者”,令牌);
var response=wait-httpClient.GetAsync(url);
返回等待BuildResponse(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.
        }
    }
}
类JWTAuthenticationStateProvider:AuthenticationStateProvider
{
private bool IsLogedIn=假;
私有CustomCredentials=null;
//private ClaimsPrincipal currentClaimsPrincipal=null;(可选)
公共任务登录(字符串用户、字符串密码)
{
凭证=登录服务(用户、密码);
//使用凭证和声明做一些事情
//我在这里引发事件以通知登录
保持会话();
}
公共任务注销()
{
go_bakcend_注销服务(凭证);
//处理索赔事宜
IsLogedIn=假;
//我在这里引发事件以通知注销
}

public override Task

谢谢。在使用页内Api调用时,管道的工作原理如何?我在登录并调用页内Api
.GetJsonAsync()后发现了这一点
我得到一个
403
返回?403?它看起来像是一个与当前问题无关的新问题。只需发布一个包含所有详细信息的新问题。别忘了调试服务器端以隔离错误。我想问题是视图没有包装
AuthorizeView
属性,因此JwtStateProvider没有被击中。我将提出另一个问题关于这一点,回到关于您的实现的主题,您是否使用JWT的到期日来确定
任务延迟(到期日)
还是您只需轮询?此外,一旦您返回403,您是否会尝试刷新一次,然后触发注销?或者这是您的
go\u backend\u RenewaringJWT\u服务的一部分?
呼叫?这只是一个想法,您应该考虑一下,并将其调整为您自己的解决方案。您应该在jwt过期之前续订,然后,合并
使用
任务进入\u backend\u续订JWT\u服务
。延迟(到期日-X)
是诀窍。创建您自己的逻辑来处理后端错误(没有Internet连接,重试,…)Hi@Danielrera,您可以说“//I在此处引发事件以通知登录”您在那里实现了什么代码?非常感谢
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);
    }
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.
        }
    }
}
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>();
}