use*_*480 7 c# blazor-server-side asp.net-core-3.0
我正在为 Asp.net 核心 3.0 Blazor 服务器端应用程序构建示例登录 razor 组件。每当代码到达 SignInAsyc 方法时,它似乎只是挂起或锁定,因为代码停止进一步执行。我还尝试通过使用 PasswordSignInAsync 方法来切换逻辑,这给了我完全相同的结果。所有代码都将在该方法之前执行,但在执行该语句时会冻结。我在这里缺少什么?
Razor 组件页面:
<div class="text-center">
<Login FieldsetAttr="fieldsetAttr" UsernameAttr="usernameAttr" PasswordAttr="passwordInput"
ButtonAttr="buttonAttr" ButtonText="Sign In" InvalidAttr="invalidAttr" />
</div>
@code {
Dictionary<string, object> fieldsetAttr =
new Dictionary<string, object>()
{
{"class", "form-group" }
};
Dictionary<string, object> usernameAttr =
new Dictionary<string, object>()
{
{"class", "form-control" },
{"type", "text" },
{"placeholder", "Enter your user name here." }
};
Dictionary<string, object> passwordInput =
new Dictionary<string, object>()
{
{"class", "form-control" },
{"type", "password" }
};
Dictionary<string, object> buttonAttr =
new Dictionary<string, object>()
{
{"type", "button" }
};
Dictionary<string, object> invalidAttr =
new Dictionary<string, object>()
{
{"class", "" },
{"style", "color: red;" }
};
Dictionary<string, object> validAttr =
new Dictionary<string, object>()
{
{"class", "" },
{"style", "color: green;" }
};
}
Run Code Online (Sandbox Code Playgroud)
剃须刀组件:
@inject SignInManager<IdentityUser> signInManager
@inject UserManager<IdentityUser> userManager
<div @attributes="FormParentAttr">
<form @attributes="LoginFormAttr">
<fieldset @attributes="FieldsetAttr">
<legend>Login</legend>
<label for="usernameId">Username</label><br />
<input @attributes="UsernameAttr" id="usernameId" @bind="UserName" /><br />
<label for="upasswordId">Password</label><br />
<input @attributes="PasswordAttr" id="passwordId" @bind="Password" /><br />
<button @attributes="ButtonAttr" @onclick="@(async e => await LoginUser())">@ButtonText</button>
@if (errorMessage != null && errorMessage.Length > 0)
{
<div @attributes="InvalidAttr">
@errorMessage
</div>
}
else if(successMessage != null && successMessage.Length > 0)
{
<div @attributes="ValidAttr">
@successMessage
</div>
}
</fieldset>
</form>
</div>
@code {
string successMessage = "";
private async Task LoginUser()
{
if(!String.IsNullOrEmpty(UserName))
{
var user = await userManager.FindByNameAsync(UserName);
var loginResult =
await signInManager.CheckPasswordSignInAsync(user, Password, false);
if(loginResult.Succeeded)
{
await signInManager.SignInAsync(user, true);
successMessage = $"{UserName}, signed in.";
errorMessage = "";
}
else
{
successMessage = "";
errorMessage = "Username or password is incorrect.";
}
}
else
{
successMessage = "";
errorMessage = "Provide a username.";
}
}
[Parameter]
public Dictionary<string, object> FormParentAttr { get; set; }
[Parameter]
public Dictionary<string, object> LoginFormAttr { get; set; }
[Parameter]
public Dictionary<string, object> FieldsetAttr { get; set; }
[Parameter]
public Dictionary<string, object> UsernameAttr { get; set; }
[Parameter]
public Dictionary<string, object> PasswordAttr { get; set; }
[Parameter]
public Dictionary<string,object> ButtonAttr { get; set; }
[Parameter]
public Dictionary<string, object> InvalidAttr { get; set; }
private string UserName { get; set; }
private string Password { get; set; }
[Parameter]
public string ButtonText { get; set; }
[Parameter]
public Dictionary<string, object> ValidAttr { get;set; }
public string errorMessage { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
Jam*_*aug 11
itminus 之前的答案以及评论中讨论的问题之一是在手动刷新、会话结束或导致刷新的链接后保持用户的状态。这会丢失用户的状态,因为 cookie 值没有设置到客户端的浏览器,这意味着下一个 HTTP 请求不包含 cookie。一种解决方案是使用静态登录/退出页面,这将允许将 cookie 发送到客户端的浏览器。
此方法改为使用 JS 将 cookie 写入客户端浏览器,从而允许 Blazor 处理所有事情。我遇到了 cookie 设置未正确设置的一些问题,因为我误解了如何AddCookie()在启动中将选项添加到 DI 容器。它使用IOptionsMonitor来使用命名选项,并使用Scheme作为键。
我已经修改了登录代码以调用将保存 cookie 的 JS。您可以在注册新用户或登录现有用户后运行此命令。
确保您 DI IOptionsMonitor<CookieAuthenticationOptions>,允许您使用方案作为键来解析命名选项。确保您使用.Get(schemeName)而不是.CurrentValue,否则您TicketDataFormat(和其他设置)将不正确,因为它将使用默认值。我花了几个小时才意识到这一点。
注意:IOptionsMonitor<CookieAuthenticationOptions>来自调用services.AddAuthentication().AddCookie(). 下面提供了一个示例。
_cookieAuthenticationOptions = cookieAuthenticationOptionsMonitor.Get("MyScheme");
...
private async Task SignInAsync(AppUser user, String password)
{
//original code from above answer
var principal = await _signInManager.CreateUserPrincipalAsync(user);
var identity = new ClaimsIdentity(
principal.Claims,
"MyScheme"
);
principal = new ClaimsPrincipal(identity);
_signInManager.Context.User = principal;
_hostAuthentication.SetAuthenticationState(Task.FromResult(new AuthenticationState(principal)));
// this is where we create a ticket, encrypt it, and invoke a JS method to save the cookie
var ticket = new AuthenticationTicket(principal, null, "MyScheme");
var value = _cookieAuthenticationOptions.TicketDataFormat.Protect(ticket);
await _jsRuntime.InvokeVoidAsync("blazorExtensions.WriteCookie", "CookieName", value, _cookieAuthenticationOptions.ExpireTimeSpan.TotalDays);
}
Run Code Online (Sandbox Code Playgroud)
然后我们编写一个 JS cookie:
window.blazorExtensions = {
WriteCookie: function (name, value, days) {
var expires;
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toGMTString();
}
else {
expires = "";
}
document.cookie = name + "=" + value + expires + "; path=/";
}
}
Run Code Online (Sandbox Code Playgroud)
这将成功地将cookie写入客户端的浏览器。如果您遇到问题,请确保您的启动使用相同的方案名称。如果不这样做,那么普通的 cookie 身份验证系统将无法正确解析回已编码的主体:
services.AddIdentityCore<AppUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>()
.AddSignInManager();
services.AddAuthentication(options =>
{
options.DefaultScheme = "MyScheme";
}).AddCookie("MyScheme", options =>
{
options.Cookie.Name = "CookieName";
});
Run Code Online (Sandbox Code Playgroud)
对于完成主义者,也可以用同样的方式实现注销:
private async Task SignOutAsync()
{
var principal = _signInManager.Context.User = new ClaimsPrincipal(new ClaimsIdentity());
_hostAuthentication.SetAuthenticationState(Task.FromResult(new AuthenticationState(principal)));
await _jsRuntime.InvokeVoidAsync("blazorExtensions.DeleteCookie", _appInfo.CookieName);
await Task.CompletedTask;
}
Run Code Online (Sandbox Code Playgroud)
还有JS:
window.blazorExtensions = {
DeleteCookie: function (name) {
document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:01 GMT";
}
}
Run Code Online (Sandbox Code Playgroud)
itm*_*nus 10
基本上,这是因为在SigninManger::SignInAsync()实际上将尝试发送一个cookie在HTTP以表示该用户在已签署的。但在这一刻Blazor服务器端处理的时候,有没有可用的HTTP响应,则各只有一个WebSocket连接(SignalR)。
简而言之,Signin 是持久化用户凭据/cookies/... 以便 WebApp 知道客户端是谁。由于您使用的是 Blazor 服务器端,因此您的客户端正在通过WebSocket 连接与服务器通信。无需通过 发送 cookie HTTP。因为你的 WebApp 已经知道当前用户是谁了。
要解决此问题,IHostEnvironmentAuthenticationStateProvider请先注册一个服务:
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
services.AddScoped<IHostEnvironmentAuthenticationStateProvider>(sp => {
// this is safe because
// the `RevalidatingIdentityAuthenticationStateProvider` extends the `ServerAuthenticationStateProvider`
var provider = (ServerAuthenticationStateProvider) sp.GetRequiredService<AuthenticationStateProvider>();
return provider;
});
Run Code Online (Sandbox Code Playgroud)
然后创建一个主体并替换旧的。
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject IHostEnvironmentAuthenticationStateProvider HostAuthentication
...
var user = await userManager.FindByNameAsync(UserName);
var valid= await signInManager.UserManager.CheckPasswordAsync(user, Password);
如果(有效)
{
var principal = await signInManager.CreateUserPrincipalAsync(user);
var identity = new ClaimsIdentity(
校长索赔,
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationDefaults.AuthenticationScheme
);
主体 = 新 System.Security.Claims.ClaimsPrincipal(identity);
signInManager.Context.User = 主体;
HostAuthentication.SetAuthenticationState(Task.FromResult(new AuthenticationState(principal)));
// 现在更新了 authState
var authState = 等待 AuthenticationStateProvider.GetAuthenticationStateAsync();
successMessage = $"{用户名},已登录。";
错误信息 = "";
}
别的
{
成功消息 = "";
errorMessage = "用户名或密码不正确。";
}
并检查authState: