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重定向回来,或去要访问的页面就可以了。

这样就成功实现登录功能了。