ASP.NET Core 6 MVC会话劫持防范求助(已配置基础安全仍被复现)
解决ASP.NET Core 6 MVC会话劫持问题的方案
核心问题原因
虽然配置了Secure、HttpOnly Cookie,但会话ID未与用户身份绑定。低权限用户替换Session ID后,系统仅验证会话存在,未检查会话对应的用户是否与当前请求的登录用户一致,导致权限越权。
具体解决步骤
- 登录时将用户唯一身份标识存入Session,绑定会话与用户身份
- 全局验证每个请求的会话用户信息与当前登录用户是否匹配,不匹配则终止请求或清除会话
示例代码
登录逻辑(AccountController)
[HttpPost] public async Task<IActionResult> Login(LoginViewModel model) { if (ModelState.IsValid) { // 验证用户身份(示例逻辑) var user = await _userManager.FindByNameAsync(model.UserName); if (user != null && await _userManager.CheckPasswordAsync(user, model.Password)) { var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, user.Id), new Claim(ClaimTypes.Name, user.UserName), new Claim(ClaimTypes.Role, (await _userManager.GetRolesAsync(user)).FirstOrDefault() ?? "User") }; var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity)); // 将用户唯一ID存入Session,绑定会话与用户 HttpContext.Session.SetString("CurrentUserId", user.Id); return RedirectToAction("Index", "Home"); } ModelState.AddModelError("", "用户名或密码错误"); } return View(model); }
全局会话验证中间件
创建中间件类:
public class SessionAuthValidationMiddleware { private readonly RequestDelegate _next; public SessionAuthValidationMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { // 跳过无需验证的路径(登录、注册等) var path = context.Request.Path.Value; if (path.StartsWith("/Account/Login") || path.StartsWith("/Account/Register") || path.StartsWith("/Home/Index")) { await _next(context); return; } if (context.User.Identity.IsAuthenticated) { var currentUserId = context.User.FindFirstValue(ClaimTypes.NameIdentifier); var sessionUserId = context.Session.GetString("CurrentUserId"); // 验证会话用户与当前登录用户是否一致 if (string.IsNullOrEmpty(sessionUserId) || sessionUserId != currentUserId) { // 不一致则清除登录状态与会话 await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); context.Session.Clear(); context.Response.Redirect("/Account/Login"); return; } } await _next(context); } }
注册中间件(Program.cs)
注意中间件顺序,需放在UseAuthentication和UseAuthorization之后:
var builder = WebApplication.CreateBuilder(args); // 注册服务 builder.Services.AddControllersWithViews(); builder.Services.AddSession(options => { options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; options.IdleTimeout = TimeSpan.FromMinutes(30); }); builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.Cookie.HttpOnly = true; options.ExpireTimeSpan = TimeSpan.FromMinutes(30); options.LoginPath = "/Account/Login"; }); var app = builder.Build(); // 配置管道 if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); // 添加自定义会话验证中间件 app.UseMiddleware<SessionAuthValidationMiddleware>(); app.UseSession(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();
登出逻辑(清除Session绑定)
[HttpPost] public async Task<IActionResult> Logout() { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); HttpContext.Session.Clear(); // 清除会话中的用户绑定信息 return RedirectToAction("Login", "Account"); }
额外增强建议
- 启用会话固定保护:登录成功后调用
HttpContext.Session.Refresh()生成新Session ID,避免攻击者提前获取旧ID - 缩短会话超时时间:减少Session ID的有效时长,降低被滥用的窗口
- 优先使用Claims存储身份信息:若无需Session存储其他数据,直接通过Claims传递用户身份,避免依赖Session,同时Claims随加密Cookie存储,安全性更高
内容的提问来源于stack exchange,提问作者dibas paudel
相关产品推荐
相关产品推荐

