在Blazor Server中使用Identity Framework时,微软的官方声明是不支持它,而是使用Razor登录页面。问题是Blazor不会写cookie。
这有几个缺点:
- 无法重复使用HTML布局文件,需要为Razor重新创建重复的布局文件
- 无法在Blazor页面上嵌入登录按钮
- 如果需要作为结账体验的一部分选择性登录,则用户体验不佳
这家伙想出了一个方法,让Blazor登录页面与Blazor WebAssembly一起工作。。。不确定他是如何解决这个问题的,也不确定类似的方法是否适用于Blazor Server。
我在考虑其他解决方案。问题是存储cookie。本地存储可以在Blazor中使用,但本地存储对于安全令牌来说是不安全的。然而,Cookie也可以通过JavaScript互操作以类似的方式进行设置。
这种方法可行吗?登录后通过JavaScript互操作设置登录cookie,然后在任何进一步的页面加载时发送cookie?有人做过吗?
对于单页管理内容,我发现了一种更简单的方法,即创建一个GatedContent组件,该组件显示登录表单,然后在登录后显示ChildContent。当然,这不会在页面刷新时保留会话,但在某些情况下有效。
这里有一个单页管理内容的解决方案:GatedContent
它将显示一个登录表单,成功登录后,显示封闭的内容。
SpinnerButton在此处定义。
@using Microsoft.AspNetCore.Identity
@using System.ComponentModel.DataAnnotations
@inject NavigationManager navManager
@inject SignInManager<ApplicationUser> signInManager
@inject UserManager<ApplicationUser> userManager;
@if (!LoggedIn)
{
<EditForm Context="formContext" class="form-signin" OnValidSubmit="@SubmitAsync" Model="this">
<DataAnnotationsValidator />
<div style="margin-bottom: 1rem; margin-top: 1rem;" class="row">
<Field For="@(() => Username)" Width="4" Required="true">
<RadzenTextBox @bind-Value="@Username" class="form-control" Style="margin-bottom: 0px;" />
</Field>
</div>
<div style="margin-bottom: 1rem" class="row">
<Field For="@(() => Password)" Width="4" Required="true">
<RadzenPassword @bind-Value="@Password" class="form-control" />
</Field>
</div>
<SpinnerButton @ref="ButtonRef" style="width:150px" Text="Login" ButtonType="@((Radzen.ButtonType)ButtonType.Submit)" ButtonStyle="@((Radzen.ButtonStyle)ButtonStyle.Primary)" OnSubmit="LogInAsync" />
@if (Error.HasValue())
{
<div class="red">@Error</div>
}
</EditForm>
}
else
{
@ChildContent
}
@code {
public SpinnerButton? ButtonRef { get; set; }
public async Task SubmitAsync() => await ButtonRef!.FormSubmitAsync().ConfigureAwait(false);
[Required]
public string Username { get; set; } = string.Empty;
[Required]
public string Password { get; set; } = string.Empty;
string? Error { get; set; }
bool LoggedIn { get; set; }
[Parameter]
public RenderFragment? ChildContent { get; set; }
public async Task LogInAsync()
{
Error = null;
try
{
var user = await userManager.FindByNameAsync(Username).ConfigureAwait(false);
if (user != null)
{
var isAdmin = await userManager.IsInRoleAsync(user, ApplicationRole.Admin).ConfigureAwait(false);
if (isAdmin)
{
var singInResult = await signInManager.CheckPasswordSignInAsync(user, Password, true).ConfigureAwait(false);
if (singInResult.Succeeded)
{
LoggedIn = true;
}
else
{
Error = "Invalid password";
}
}
else
{
Error = "User is not Admin";
}
}
else
{
Error = "Username not found";
}
}
catch (Exception ex)
{
Error = ex.Message;
}
}
}
像这样使用
<GatedContent>
This is an admin page.
</GatedContent>
当然,这个解决方案不会在页面重新加载后保留状态,但它适用于简单的管理页面。
我的解决方法是这样的:我创建了一个类似的SignInController
public class SignInController : ControllerBase
{
private readonly SignInManager<IdentityUser> _signInManager;
public SignInController(SignInManager<IdentityUser> signInManager)
{
_signInManager = signInManager;
}
[HttpPost("/signin")]
public IActionResult Index([FromForm]string username, [FromForm]string password, [FromForm]string rememberMe)
{
bool.TryParse(rememberMe, out bool res);
var signInResult = _signInManager.PasswordSignInAsync(username, password, res, false);
if (signInResult.Result.Succeeded)
{
return Redirect("/");
}
return Redirect("/login/"+ signInResult.Result.Succeeded);
}
[HttpPost("/signout")]
public async Task<IActionResult> Logout()
{
if (_signInManager.IsSignedIn(User))
{
await _signInManager.SignOutAsync();
}
return Redirect("/");
}
}
我有一把像这样的登录剃须刀:
@page "/login"
<form action="/signin" method="post">
<div class="form-group">
<p> Username </p>
<input id="username" Name="username" />
</div>
<div class="form-group">
<p>password<p/>
<input type="password" id="password" />
</div>
<div class="form-group">
<p> remember me? <p/>
<input type="checkbox" id="rememberMe" />
</div>
<button type="Submit" Text="login" />
</form>
此问题是由signInManager引起的,只有当您使用HTTP/S请求时,signInManager才起作用。否则,它将抛出Exceptions。GitHub上的dotnet/aspcore.net Repo上也有一些关于这一点的报道。
目前,这是(我知道的(将其与blazor服务器一起使用的唯一方法。