You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core SignOutAsync会话问题:跨标签页未失效引发越权操作

问题分析

当前场景的核心问题在于:

  • 登出后旧标签页的缓存页面未失效,浏览器仍允许用户发起操作请求
  • 权限校验仅在页面初始加载时生效,后续提交操作未做实时权限验证
  • Cookie会话切换后,旧标签页的请求会携带新用户的Cookie,导致无权限用户绕过页面权限校验执行敏感操作
解决方案

1. 强化Cookie会话的有效性校验

修改Startup.cs中的Cookie配置,添加会话验证逻辑,确保无效/过期会话被立即拦截并重定向:

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.LoginPath = "/UserAccount";
        options.ExpireTimeSpan = TimeSpan.FromMinutes(10);
        options.SlidingExpiration = true;
        // 提升Cookie安全性,防止XSS窃取
        options.Cookie.HttpOnly = true;
        options.Cookie.SameSite = SameSiteMode.Strict;
        // 每次请求时校验会话有效性
        options.Events = new CookieAuthenticationEvents
        {
            OnValidatePrincipal = async context =>
            {
                // 校验用户会话是否有效(可扩展为从数据库/缓存校验用户状态)
                var userDataClaim = context.Principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.UserData);
                if (userDataClaim == null || string.IsNullOrEmpty(userDataClaim.Value))
                {
                    context.RejectPrincipal();
                    await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
                }
            },
            // 区分AJAX请求与普通页面请求的重定向逻辑
            OnRedirectToLogin = context =>
            {
                if (context.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
                {
                    context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                    return Task.CompletedTask;
                }
                context.Response.Redirect(context.RedirectUri);
                return Task.CompletedTask;
            }
        };
    });

2. 登出时强制清除页面缓存

修改登出方法,在登出的同时禁止浏览器缓存旧页面,避免用户通过旧标签页访问受限内容:

public async Task<IActionResult> Logout()
{
    await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    // 设置响应头,强制浏览器清除缓存
    Response.Headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
    Response.Headers["Pragma"] = "no-cache";
    Response.Headers["Expires"] = "0";
    return RedirectToAction("Index", "Home");
}

3. 敏感操作添加实时权限校验

针对采购订单提交这类敏感操作,在后端Action中直接校验当前用户的权限,不依赖页面级的登录校验:

[HttpPost]
public async Task<IActionResult> SubmitPurchaseOrder(PurchaseOrderModel model)
{
    // 获取当前用户信息
    var userDataClaim = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.UserData);
    if (userDataClaim == null)
    {
        return RedirectToAction("Index", "UserAccount");
    }
    var user = JsonSerializer.Deserialize<User>(userDataClaim.Value);
    // 校验用户是否拥有采购订单提交权限
    if (!user.HasPermission("Purchase.Submit"))
    {
        return Forbid(); // 或重定向到无权限提示页面
    }
    // 执行订单提交逻辑
    // ...
    return RedirectToAction("OrderList");
}

4. 前端主动检测会话状态

在受限页面添加定时检测,主动发现会话失效并跳转登录页,避免用户在无效状态下操作:

// 每30秒检测一次会话状态
setInterval(async () => {
    try {
        const response = await fetch('/Home/CheckSession', {
            headers: { 'X-Requested-With': 'XMLHttpRequest' }
        });
        if (response.status === 401) {
            window.location.href = '/UserAccount';
        }
    } catch (err) {
        console.error('会话检测失败:', err);
    }
}, 30000);

对应的后端检测接口:

[HttpGet]
public IActionResult CheckSession()
{
    return User.Identity.IsAuthenticated ? Ok() : Unauthorized();
}

内容的提问来源于stack exchange,提问作者Prakash

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 11:50:44