Session 复活
本文仅适用于 Asp.Net Core MVC
在使用 Cookies + Session 做身份验证时,Session 过期导致频繁登录是不可避免的问题,设置 Session 过期时间不是最佳的解决办法。Fireasy 提供了一种机制,即在 Session 过期后的第一次请求,通过拿取 Cookies 里的用户ID再次构建 Session 信息,以实现会话的长效性。
在 Asp.Net MVC
里,可以在 Global.cs 的 Session_Start 方法里处理这件事,但是在 Asp.Net Core MVC
里,必须采用中间件技术。
你需要在 Startup.ConfigureServices 方法里使用 AddSessionRevive 方法来指定一个实现了 ISessionReviveNotification
接口的类型,同时在 Configure 方法里使用 UseSessionRevive 引入中间件。如下所示:
namespace demo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSessionRevive<SessionReviveNotification>();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = new PathString("/login");
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
#if NETCOREAPP2_2
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
#elif NETCOREAPP3_1
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider svp)
{
if (env.IsDevelopment())
{
#endif
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
//添加静态文件映射
app.UseStaticFiles();
app.UseSession();
#if NETCOREAPP2_2
app.UseAuthentication();
app.UseSessionRevive();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "areas",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
#elif NETCOREAPP3_1
app.UseRouting();
app.UseAuthentication();
app.UseSessionRevive();
app.UseAuthorization();
app.UseEndpoints(c =>
{
c.MapControllerRoute("areas", "{area:exists}/{controller=Home}/{action=Index}/{id?}");
c.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
#endif
}
}
}
SessionReviveNotification
类实现 ISessionReviveNotification
接口的 InvokeAsync 方法。注意 IAdminService
不能从构造函数里注入。如下所示:
/// <summary>
/// Session 复活时自动设置 <see cref="SessionContext"/> 对象。
/// </summary>
public class SessionReviveNotification : ISessionReviveNotification
{
public async Task InvokeAsync(HttpContext context)
{
var adminService = context.RequestServices.GetRequiredService<IAdminService>();
var userId = GetIdentity(context);
if (userId != 0)
{
//根据UserId获取用户信息
var user = await adminService.GetUserAsync(userId);
if (user != null)
{
var session = new SessionContext
{
UserID = userId,
UserName = user.Name,
OrgID = user.OrgID
};
SetSession(context, session);
}
}
}
// 从 Cookies 里获取用户ID
private static int GetIdentity(HttpContext context)
{
var claim = context.User.Claims.FirstOrDefault(s => s.Type == ClaimTypes.Sid);
return claim == null ? 0 : claim.Value.To<int>();
}
// 将信息写到 Session
private static void SetSession(HttpContext context, SessionContext session)
{
var json = new JsonSerializer().Serialize(session);
context.Session.Set("__user__info", Encoding.UTF8.GetBytes(json));
}
}