blazor SignInAsync实现登录
前言
在MVC中我们经常使用内置的Identity来实现登录,简单快捷。当然,还有其他方式来实现,但是在blazor中使用 SignInAsync会出现这样的错误 "The response headers cannot be modified because the response has already started"。
我通过依赖注入IHttpContextAccessor _httpContextAccessor,来获取HttpContext调用SignInAsync方法。blazor是SignaIR 长轮询来实现的,所以会出现这样的错误。我们需要创建一个新的请求来实现。
实现
ASP.NET Core 项目是可以同时托管多种不同的程序的。例如,在一个项目能运行Webapi和Blazor(注意,我指的并不是在一个解决方案),我们需要创建一个控制器,添加登录方法。
//AccountController.cs
public class AccountController :ControllerBase
{
private readonly IDbContextFactory _dbFactory;
private readonly IDataProtectionProvider _dataProtectionProvider;
public AccountController(IDataProtectionProvider dataProtectionProvider, IDbContextFactory dbFactory)
{
_dbFactory = dbFactory;
_dataProtectionProvider = dataProtectionProvider;
}
///
/// 后端登录
///
///
///
[HttpGet]
[Route("Login")]
//[AuthorizationVerify]
[Authorize(Policy = "AtLeast21")]
public async Task Login(string token)
{
var dataProtect = _dataProtectionProvider.CreateProtector("Login");
var data = dataProtect.Unprotect(token);
var parts = data.Split('|');
using var context = _dbFactory.CreateDbContext();
var user = await context.Admin.FirstOrDefaultAsync(x => !x.IsDelete && x.Uno.Equals(parts[0]) && x.PassWord.Equals(parts[1]));
if (user != null)
{
#region 用户信息凭证
AuthenticationProperties props = null;
var claims = new List() {
new Claim(ClaimTypes.Sid, user.Uno),
new Claim(ClaimTypes.Name,user.UserName),
new Claim(ClaimTypes.Uri, user.ImageUrl),
};
props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(TimeSpan.FromDays(1))
};
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme)),
props);
#endregion
return Redirect("/Admin/index");
}
else
{
return Redirect($"/Login/{true}");
}
}
}
上面,我们创建了一个控制器,添加了登录验证方法。但想要同时运行Webapi和Blazor,还需要对Startup.cs文件 Configure方法终结点进行修改。
// Startup.cs
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
endpoints.MapControllers();
// 新添加的控制器终结点
// 项目会按照终结点的顺序来进行访问
// 先走上面的Blazor,如果找不对应的路由信息
// 会走到webapi的控制器路由来查找
});
接下来,就是Login.Razor组件的部分了。这里我们需要通过加密处理账号和密码,通过_navigationManager来实现调用。这里不能使用httpclient的方式,应该是请求会被判断为服务器发出,在浏览器内的NetWork中没有记录。(只是猜测,也可能是长轮询的问题)
//Login.Razor.cs
//账号密码加密处理
//需要注入IDataProtectionProvider _dataProtectionProvider
var dataProtect = _dataProtectionProvider.CreateProtector("Login");
var input = dataProtect.Protect($"{model.Username}|{model.Password}");
//需要在组件中注入NavigationManager _navigationManager
_navigationManager.NavigateTo("/api/Account/Login?token=" + input, true);
控制器中的Login方法通过Redirect重定向回来,或去要访问的页面就可以了。
这样就成功实现登录功能了。