我的令牌到期时遇到问题。令牌在服务器端过期,但在客户端,只有当我按下F5时,它才会显示过期。
App.razor代码
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
<MCF.BlazorClient.Components.RedirectToLogin></MCF.BlazorClient.Components.RedirectToLogin>
</NotAuthorized>
<Authorizing>
<h1>Authentication in progress</h1>
</Authorizing>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
我的AuthService代码:
public async Task<LoginResult> Login(LoginModel loginModel)
{
var result = await _httpClient.PostJsonAsync<LoginResult>(GetApi("api/login"), loginModel);
if (result.Successful)
{
await SetTokenAsync(result.Token, result.Expiry);
((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsAuthenticated(result.Token);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", result.Token);
return result;
}
return result;
}
public async Task SetTokenAsync(string token, DateTime expiry = default)
{
if (token == null)
{
await _localStorage.RemoveItemAsync("authToken");
await _localStorage.RemoveItemAsync("authTokenExpiry");
}
else
{
await _localStorage.SetItemAsync("authToken", token);
await _localStorage.SetItemAsync("authTokenExpiry", expiry);
}
}
public async Task<string> GetTokenAsync()
{
var expiry = await _localStorage.GetItemAsync<string>("authTokenExpiry");
if (expiry != null)
{
if (DateTime.Parse(expiry.ToString()) > DateTime.Now)
{
return await _localStorage.GetItemAsync<string>("authToken");
}
else
{
await SetTokenAsync(null);
}
}
return null;
}
public async Task Logout()
{
await _localStorage.RemoveItemAsync("authToken");
((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsLoggedOut();
_httpClient.DefaultRequestHeaders.Authorization = null;
}
自定义ApiAuthenticationStateProvider 的代码
namespace ABC
{
public class ApiAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly HttpClient _httpClient;
private readonly ILocalStorageService _localStorage;
public List<Claim> Claims { get; set; } = new List<Claim>();
public ApiAuthenticationStateProvider(HttpClient httpClient, ILocalStorageService localStorage)
{
_httpClient = httpClient;
_localStorage = localStorage;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var savedToken = await _localStorage.GetItemAsync<string>("authToken");
var expiry = await _localStorage.GetItemAsync<string>("authTokenExpiry");
var anonymousState = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
// Not authenticated
if (string.IsNullOrWhiteSpace(savedToken))
{
return anonymousState;
}
var claims = ParseClaimsFromJwt(savedToken);
// Checks the exp field of the token
if (DateTime.Parse(expiry.ToString()) < DateTime.Now)
{
await _localStorage.RemoveItemAsync("authToken");
await _localStorage.RemoveItemAsync("authTokenExpiry");
MarkUserAsLoggedOut();
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
}
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", savedToken);
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims, "jwt")));
}
public void MarkUserAsAuthenticated(string token)
{
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(token), "jwt"));
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);
}
private IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
{
var claims = new List<Claim>();
var payload = jwt.Split('.')[1];
var jsonBytes = ParseBase64WithoutPadding(payload);
var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles);
if (roles != null)
{
if (roles.ToString().Trim().StartsWith("["))
{
var parsedRoles = JsonSerializer.Deserialize<string[]>(roles.ToString());
foreach (var parsedRole in parsedRoles)
{
claims.Add(new Claim(ClaimTypes.Role, parsedRole));
}
}
else
{
claims.Add(new Claim(ClaimTypes.Role, roles.ToString()));
}
keyValuePairs.Remove(ClaimTypes.Role);
}
claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString())));
return claims;
}
private byte[] ParseBase64WithoutPadding(string base64)
{
switch (base64.Length % 4)
{
case 2: base64 += "=="; break;
case 3: base64 += "="; break;
}
return Convert.FromBase64String(base64);
}
}
}
Program.cs中用于注册身份验证提供商的代码:
builder.Services.AddScoped<ApiAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(provider => provider.GetRequiredService<ApiAuthenticationStateProvider>());
builder.Services.AddScoped<IAuthService, AuthService>();
最后在我的MainLayout.cs 中编码
@inherits LayoutComponentBase
@inject IJSRuntime JsInterop
<TelerikRootComponent>
<AuthorizeView>
<Authorized>
<div class="sidebar">
<DynamicNavMenu />
</div>
</Authorized>
<Authorizing>
<h1>Authentication in progress</h1>
<p>You can only see this content while authentication is in progress.</p>
</Authorizing>
</AuthorizeView>
<div class="main">
<div class="top-row px-4">
<LoginDisplay />
<TelerikMenu Data="@MenuItems" OnClick="@((MenuItem item) => ChangeTheme(item))" IconField="TelerikIcon" ImageUrlField="RasterImage" IconClassField="CustomFontIcon">
</TelerikMenu>
</div>
<div class="content px-4" id="BlazorSection">
@Body
</div>
<div></div>
</div>
@code {
[CascadingParameter] private Task<AuthenticationState> authenticationStateTask { get; set; }
private bool isAuthenticated = false;
public List<MenuItem> MenuItems { get; set; }
public class MenuItem
{
public string Text { get; set; }
public List<MenuItem> Items { get; set; }
}
protected override void OnInitialized()
{
base.OnInitialized();
}
private async Task IsAuthenticated()
{
var authState = await authenticationStateTask;
var user = authState.User;
if (user.Identity.IsAuthenticated)
{
isAuthenticated = true;
}
else
{
isAuthenticated = false;
}
}
}
可能是Program.cs
中缺少app.UseAuthentication()
。它在设置服务之后,在app.Run()
之前。