Authentication 使用标识的Blazor服务器端自定义登录在重新启动后丢失日志状态

Authentication 使用标识的Blazor服务器端自定义登录在重新启动后丢失日志状态,authentication,asp.net-identity,blazor-server-side,Authentication,Asp.net Identity,Blazor Server Side,我实现了自己的解决方案,无需刷新页面即可登录。但是,我无法理解为什么在应用程序重新启动(新的调试运行)时会丢失登录状态 启动 public void ConfigureServices(IServiceCollection services) { services.AddDbContext<DatabaseContext>(options => options.UseSqlServer(Configuration.GetConnectionStr

我实现了自己的解决方案,无需刷新页面即可登录。但是,我无法理解为什么在应用程序重新启动(新的调试运行)时会丢失登录状态

启动

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<DatabaseContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")), ServiceLifetime.Transient);

        services.AddIdentity<User, Role>(options =>
        { // options...
        }).AddEntityFrameworkStores<DatabaseContext>().AddDefaultTokenProviders();
        services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo("/keys")).SetApplicationName("App").SetDefaultKeyLifetime(TimeSpan.FromDays(90));
        services.AddRazorPages();
        services.AddServerSideBlazor().AddCircuitOptions(options =>
        {
            options.DetailedErrors = true;
        });

        services.AddSession();
        services.AddSignalR();
        services.AddBlazoredLocalStorage();
        services.AddHttpClient();

        services.Configure<CookiePolicyOptions>(options =>
        {
            options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });
        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();

        services.AddAuthorization(options =>
        { // options...
        });

        // Add application services.
        services.AddScoped<AuthenticationStateProvider, IdentityAuthenticationStateProvider>();

        // .. services

        services.AddMvc(options =>
        {
            options.OutputFormatters.Add(new XmlSerializerOutputFormatter());
        }).AddSessionStateTempDataProvider();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseSession();
        app.UseStaticFiles();
        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            endpoints.MapBlazorHub();
            endpoints.MapFallbackToPage("/_Host");
        });
    }
public void配置服务(IServiceCollection服务)
{
services.AddDbContext(options=>options.UseSqlServer(Configuration.GetConnectionString(“DefaultConnection”))、ServiceLifetime.Transient);
服务.附加性(选项=>
{//选项。。。
}).AddEntityFrameworkStores().AddDefaultTokenProviders();
services.AddDataProtection().persistKeyStoreFilesystem(new DirectoryInfo(“/keys”)).SetApplicationName(“App”).SetDefaultKeyLifetime(TimeSpan.FromDays(90));
services.AddRazorPages();
services.AddServerSideBlazor().AddCircuitOptions(选项=>
{
options.DetailedErrors=true;
});
services.AddSession();
services.AddSignalR();
AddBlazoredLocalStorage();
services.AddHttpClient();
配置(选项=>
{
options.checkApprovered=context=>true;
options.MinimumSameSitePolicy=SameSiteMode.None;
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
services.AddAuthorization(选项=>
{//选项。。。
});
//添加应用程序服务。
services.addScope();
//…服务
services.AddMvc(选项=>
{
options.OutputFormatters.Add(新的XmlSerializerOutputFormatter());
}).AddSessionStateTempDataProvider();
}
public void配置(IApplicationBuilder应用程序、IWebHostEnvironment环境)
{
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
其他的
{
app.UseExceptionHandler(“/Error”);
app.UseHsts();
}
app.UseSession();
app.UseStaticFiles();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(端点=>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage(“/_主机”);
});
}
授权控制员

    [HttpPost("Login")]
    [AllowAnonymous]
    public async Task<IActionResult> Login(LoginViewModel model)
    {
        if (ModelState.IsValid)
        {
            await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

            Microsoft.AspNetCore.Identity.SignInResult result = await signInMgr.PasswordSignInAsync(model.LEmail, model.LPassword, model.RememberMe, lockoutOnFailure: false);

            if (result.Succeeded)
                return Ok();

            return BadRequest(string.Join(", ", ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage)));
        }

        return BadRequest();
    }

    [HttpGet("UserInfo")]
    public UserInfo UserInfo()
    {
        return new UserInfo
        {
            IsAuthenticated = User.Identity.IsAuthenticated,
            UserName = User.Identity.Name,
            ExposedClaims = User.Claims.ToDictionary(c => c.Type, c => c.Value)
        };
    }
[HttpPost(“登录”)]
[异名]
公共异步任务登录(LoginViewModel模型)
{
if(ModelState.IsValid)
{
等待HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
Microsoft.AspNetCore.Identity.SignInResult结果=等待signInMgr.PasswordSignInAsync(model.LEmail、model.LPassword、model.RememberMe、lockoutOnFailure:false);
if(result.successed)
返回Ok();
返回BadRequest(string.Join(“,”,ModelState.Values.SelectMany(x=>x.Errors.Select(x=>x.ErrorMessage));
}
返回请求();
}
[HttpGet(“用户信息”)]
公共用户信息用户信息()
{
返回新用户信息
{
IsAuthenticated=User.Identity.IsAuthenticated,
用户名=User.Identity.Name,
ExposedClaims=User.Claims.ToDictionary(c=>c.Type,c=>c.Value)
};
}
我相信问题出在我的AuthenticationStateProvider实现中

public class IdentityAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider
{
    readonly AuthorizeService authorizeSvc;
    readonly IServiceScopeFactory scopeFactory;
    readonly IdentityOptions options;

    UserInfo userInfoCache;

    protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30);

    public IdentityAuthenticationStateProvider(AuthorizeService authorizeService, ILoggerFactory loggerFactory, IServiceScopeFactory serviceScopeFactory, IOptions<IdentityOptions> optionsAccessor) : base(loggerFactory)
    {
        authorizeSvc = authorizeService;
        scopeFactory = serviceScopeFactory;
        options = optionsAccessor.Value;
    }

    public async Task LoginAsync(LoginViewModel model)
    {
        await authorizeSvc.LoginAsync(model);

        NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
    }

    public async Task RegisterAsync(RegisterViewModel register)
    {
        await authorizeSvc.RegisterAsync(register);

        NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
    }

    public async Task LogoutAsync()
    {
        await authorizeSvc.LogoutAsync();

        userInfoCache = null;

        NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
    }

    async Task<UserInfo> GetUserInfoAsync()
    {
        if (userInfoCache != null && userInfoCache.IsAuthenticated)
            return userInfoCache;

        userInfoCache = await authorizeSvc.GetUserInfo();

        return userInfoCache;
    }

    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        ClaimsIdentity identity = new ClaimsIdentity();

        try
        {
            UserInfo userInfo = await GetUserInfoAsync();

            if (userInfo.IsAuthenticated)
            {
                IEnumerable<Claim> claims = new[] { new Claim(ClaimTypes.Name, userInfoCache.UserName) }.Concat(userInfoCache.ExposedClaims.Select(c => new Claim(c.Key, c.Value)));

                identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
            }
        }
        catch (HttpRequestException ex)
        {
            Console.WriteLine("Request failed:" + ex.ToString());
        }

        return new AuthenticationState(new ClaimsPrincipal(identity));
    }

    protected override async Task<bool> ValidateAuthenticationStateAsync(AuthenticationState authenticationState, CancellationToken cancellationToken)
    {
        // Get the user manager from a new scope to ensure it fetches fresh data
        IServiceScope scope = scopeFactory.CreateScope();
        try
        {
            UserManager<User> userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>();

            return await ValidateSecurityStampAsync(userManager, authenticationState.User);
        }
        finally
        {
            if (scope is IAsyncDisposable asyncDisposable)
                await asyncDisposable.DisposeAsync();
            else
                scope.Dispose();
        }
    }

    async Task<bool> ValidateSecurityStampAsync(UserManager<User> userManager, ClaimsPrincipal principal)
    {
        User user = await userManager.GetUserAsync(principal);

        if (user is null)
            return false;
        else if (!userManager.SupportsUserSecurityStamp)
            return true;

        string principalStamp = principal.FindFirstValue(options.ClaimsIdentity.SecurityStampClaimType);
        string userStamp = await userManager.GetSecurityStampAsync(user);

        return principalStamp == userStamp;
    }
}
公共类标识AuthenticationStateProvider:RevalidatingServerAuthenticationStateProvider
{
只读授权服务授权SVC;
只读IServiceScopeFactory范围工厂;
只读标识选项选项;
用户信息用户信息缓存;
受保护的覆盖TimeSpan RevalidationInterval=>TimeSpan.FromMinutes(30);
公共标识AuthenticationStateProvider(AuthorizeService AuthorizeService、ILoggerFactory loggerFactory、IServiceScopeFactory serviceScopeFactory、IOOptions Accessor):基本(loggerFactory)
{
authorizeSvc=授权服务;
scopeFactory=serviceScopeFactory;
options=optionAccessor.Value;
}
公共异步任务LoginAsync(LoginViewModel模型)
{
等待授权svc.LoginAsync(模型);
NotifyAuthenticationStateChanged(GetAuthenticationStateAync());
}
公共异步任务寄存器同步(RegisterViewModel寄存器)
{
等待授权svc.RegisterAsync(寄存器);
NotifyAuthenticationStateChanged(GetAuthenticationStateAync());
}
公共异步任务LogoutAsync()
{
等待authorizeSvc.LogoutAsync();
userInfoCache=null;
NotifyAuthenticationStateChanged(GetAuthenticationStateAync());
}
异步任务GetUserInfoAsync()
{
if(userInfoCache!=null&&userInfoCache.IsAuthenticated)
返回userInfoCache;
userInfoCache=await authorizeSvc.GetUserInfo();
返回userInfoCache;
}
公共重写异步任务GetAuthenticationStateAync()
{
ClaimsIdentity identity=新的ClaimsIdentity();
尝试
{
UserInfo UserInfo=await GetUserInfoAsync();
如果(userInfo.IsAuthenticated)
{
IEnumerable claims=new[]{new claims(ClaimTypes.Name,userInfoCache.UserName)}.Concat(userInfoCache.ExposedClaims.Select(c=>newclaims(c.Key,c.Value));
标识=新的索赔实体(索赔、CookieAuthenticationDefaults.AuthenticationScheme);
}
}
捕获(HttpRequestException-ex)
{
Console.WriteLine(“请求失败:+ex.ToString());
}
返回新的AuthenticationState(新的ClaimsPrincipal(identity));
}
受保护的覆盖异步任务ValidateAuthenticationStateSync(AuthenticationState AuthenticationState,CancellationToken CancellationToken)
{
//从新作用域获取用户管理器,以确保它获取新数据
IServiceScope范围=范围
    public async Task<UserInfo> GetUserInfo()
    {
        HttpContext context = contextAccessor.HttpContext;

        HttpClient client = clientFactory.CreateClient();
        client.BaseAddress = new Uri($"{context.Request.Scheme}://{context.Request.Host}");

        string json = await client.GetStringAsync("api/Authorize/UserInfo");

        return Newtonsoft.Json.JsonConvert.DeserializeObject<UserInfo>(json);
    }
    async Task<string> GetCookiesAsync()
    {
        try
        {
            return $".AspNetCore.Identity.Application={await jsRuntime.InvokeAsync<string>("getLoginCookies")}";
        }
        catch
        {
            return $".AspNetCore.Identity.Application={httpContextAccessor.HttpContext.Request.Cookies[".AspNetCore.Identity.Application"]}";
        }
    }

    public async Task<UserInfo> GetUserInfoAsync()
    {
        if (userInfoCache != null && userInfoCache.IsAuthenticated)
            return userInfoCache;

        userInfoCache = await authorizeSvc.GetUserInfo(await GetCookiesAsync());

        return userInfoCache;
    }
    public async Task<UserInfo> GetUserInfo(string cookie)
    {
        string json = await CreateClient(cookie).GetStringAsync("api/Authorize/UserInfo");

        return Newtonsoft.Json.JsonConvert.DeserializeObject<UserInfo>(json);
    }

    HttpClient CreateClient(string cookie = null)
    {
        HttpContext context = contextAccessor.HttpContext;

        HttpClient client = clientFactory.CreateClient();
        client.BaseAddress = new Uri($"{context.Request.Scheme}://{context.Request.Host}");

        if(!string.IsNullOrEmpty(cookie))
            client.DefaultRequestHeaders.Add("Cookie", cookie);

        return client;
    }