我有一个包含客户端、服务器和共享项目的 Blazor WASM 项目。
问题
当我向我的控制器发出 Post 请求时,如果我添加
ActionResult
属性(即使我已登录),它也不想输入 [Authorize(Roles = "User")]
。但是,如果我添加 [AllowAnonymous]
属性,它就可以正常工作。
[HttpPost]
[Route("PostOutput")]
[Authorize(Roles = "User")]
public async Task<ActionResult<string>> PostOutput([FromBody] string json)
...
但是在客户端中,无论哪种方式都可以正常工作(只要我先登录)
<AuthorizeView>
<NotAuthorized>
@{
navigationManager.NavigateTo("/login");
}
</NotAuthorized>
<Authorized>
...
使用它可以访问页面,如果我像你期望的那样首先登录,当我将此属性添加到页面时它甚至可以工作
@attribute [Authorize(Roles = "User")]
。所以在客户端一切都按预期工作,但由于某种原因在服务器上没有。
我在这里错过了什么非常明显的东西吗?
项目设置
我设置客户端的方式是这样的
Program.cs(客户端)
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddHttpClient<IAuthService, AuthService>(client =>
{
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
}).AddHttpMessageHandler<CustomAuthorizationHandler>();
builder.Services.AddHttpClient<IDashboardService, DashboardService>(client =>
{
client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
}).AddHttpMessageHandler<CustomAuthorizationHandler>();
builder.Services.AddBlazoredSessionStorage();
builder.Services.AddTransient<CustomAuthorizationHandler>();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
builder.Services.AddAuthorizationCore();
await builder.Build().RunAsync();
随着
CustomAuthorizationHandler
看起来像这样
public class CustomAuthorizationHandler : DelegatingHandler
{
private ISessionStorageService _sessionStorageService;
public CustomAuthorizationHandler(ISessionStorageService sessionStorageService)
{
_sessionStorageService = sessionStorageService;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var jwtToken = await _sessionStorageService.GetItemAsync<string>("UserSession");
if (jwtToken != null)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwtToken);
}
return await base.SendAsync(request, cancellationToken);
}
}
和
CustomAuthenticationStateProvider
public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly ISessionStorageService _sessionStorageService;
private ClaimsPrincipal _anonymous = new ClaimsPrincipal(new ClaimsIdentity());
public CustomAuthenticationStateProvider(ISessionStorageService sessionStorageService)
{
_sessionStorageService = sessionStorageService;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
try
{
var userSession = await _sessionStorageService.ReadEncryptedItemAsync<UserSessionModel>("UserSession");
if (userSession == null)
{
return await Task.FromResult(new AuthenticationState(_anonymous));
}
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
{
new Claim(ClaimTypes.Name, userSession.Username),
new Claim(ClaimTypes.Role, userSession.Role)
}, "JwtAuth"));
return await Task.FromResult(new AuthenticationState(claimsPrincipal));
}
catch (Exception e)
{
return await Task.FromResult(new AuthenticationState(_anonymous));
}
}
public async Task UpdateAuthenticationStateAsync(UserSessionModel userSession)
{
ClaimsPrincipal claimsPrincipal;
if (userSession != null)
{
claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
{
new Claim(ClaimTypes.Name, userSession.Username),
new Claim(ClaimTypes.Role, userSession.Role)
}));
userSession.ExpireDateTime = DateTime.Now.AddSeconds(userSession.ExpiresIn);
await _sessionStorageService.SaveItemEncryptedAsync("UserSession", userSession);
}
else
{
claimsPrincipal = _anonymous;
await _sessionStorageService.RemoveItemAsync("UserSession");
}
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(claimsPrincipal)));
}
public async Task<string> GetToken()
{
string result = String.Empty;
try
{
var userSession = await _sessionStorageService.ReadEncryptedItemAsync<UserSessionModel>("UserSession");
if (userSession != null && DateTime.Now < userSession.ExpireDateTime)
{
result = userSession.Token;
}
}
catch (Exception)
{
}
return result;
}
}
SessionStorageServiceExtension.cs
public static class SessionStorageServiceExtension
{
public static async Task SaveItemEncryptedAsync<T>(this ISessionStorageService sessionsStorageService, string key, T item)
{
var itemJson = JsonSerializer.Serialize(item);
var itemJsonBytes = Encoding.UTF8.GetBytes(itemJson);
var base64Json = Convert.ToBase64String(itemJsonBytes);
await sessionsStorageService.SetItemAsync(key, base64Json);
}
public static async Task<T> ReadEncryptedItemAsync<T>(this ISessionStorageService sessionsStorageService, string key)
{
var base64String = await sessionsStorageService.GetItemAsync<string>(key);
var itemJsonBytes = Convert.FromBase64String(base64String);
var itemJson = Encoding.UTF8.GetString(itemJsonBytes);
var item = JsonSerializer.Deserialize<T>(itemJson);
return item;
}
}
和Program.cs(服务器)
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("Default") ?? throw new NullReferenceException("No connection string in config!");
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.RequireHttpsMetadata = false;
o.SaveToken = true;
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(JwtAuthenticationManager.JWT_SECURITY_KEY)),
ValidateIssuer = false,
ValidateAudience = false
};
});
builder.Services.AddSingleton<ProductService>();
builder.Services.AddTransient<UserAccountService>();
builder.Services.AddDbContextFactory<CoverAIDbContext>((DbContextOptionsBuilder options) => options.UseSqlServer(connectionString));
var app = builder.Build();