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;
}