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

ASP.NET Identity:如何在自定义筛选器属性中验证Auth Token

嘿,关于你在ASP.NET Identity里验证Cookie存储的Auth Token这个需求,我给你整理了一套可行的实现方案,一步步来:

整体思路梳理

首先,你把Auth Token放在HttpOnly+Secure的Cookie里防XSS,同时用XSRF Token防CSRF,这个方案本身是很合理的。服务器端的核心逻辑就是:从请求Cookie中提取Auth Token,用ASP.NET Identity的原生验证机制校验Token有效性,同时校验XSRF Token的一致性,最后把验证通过的用户身份注入到请求上下文里。

具体实现步骤

1. 确保Token生成时正确写入Cookie

先在你的/token接口里,把Auth Token和XSRF Token分别写入对应的Cookie:

private async Task GenerateAndSetTokens(ApplicationUser user, HttpContext context)
{
    // 生成ASP.NET Identity的Auth Token
    var authToken = await _signInManager.CreateUserTokenAsync(user, "Default", "AuthToken");
    // 写入HttpOnly、Secure的Auth Token Cookie
    context.Response.Cookies.Append("AuthToken", authToken, new CookieOptions
    {
        HttpOnly = true,
        Secure = context.Request.IsHttps, // 生产环境固定为true
        SameSite = SameSiteMode.Strict,
        Expires = DateTimeOffset.UtcNow.AddHours(1)
    });

    // 生成XSRF Token(这里用Guid示例,你可以换成加密字符串)
    var xsrfToken = Guid.NewGuid().ToString("N");
    // 写入非HttpOnly的XSRF Token Cookie(方便Angular读取)
    context.Response.Cookies.Append("XSRF-TOKEN", xsrfToken, new CookieOptions
    {
        Secure = context.Request.IsHttps,
        SameSite = SameSiteMode.Lax,
        Expires = DateTimeOffset.UtcNow.AddHours(1)
    });

    // 可选:把XSRF Token和用户绑定存储(比如Redis/Session),后续验证更严格
    await _redisCache.SetStringAsync($"XSRF:{user.Id}", xsrfToken, TimeSpan.FromHours(1));
}

2. 自定义身份验证筛选器

创建一个继承自AuthorizeAttribute和IAuthorizationFilter的筛选器,在里面完成Token验证逻辑:

public class CookieAuthTokenFilter : AuthorizeAttribute, IAuthorizationFilter
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly IDistributedCache _redisCache; // 可选,用于XSRF Token的会话绑定验证

    public CookieAuthTokenFilter(UserManager<ApplicationUser> userManager, 
                                 SignInManager<ApplicationUser> signInManager,
                                 IDistributedCache redisCache)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _redisCache = redisCache;
    }

    public async void OnAuthorization(AuthorizationFilterContext context)
    {
        // 步骤1:提取Auth Token
        var authToken = context.HttpContext.Request.Cookies["AuthToken"];
        if (string.IsNullOrWhiteSpace(authToken))
        {
            context.Result = new UnauthorizedResult();
            return;
        }

        // 步骤2:验证XSRF Token
        var xsrfTokenHeader = context.HttpContext.Request.Headers["X-XSRF-TOKEN"].FirstOrDefault();
        var xsrfTokenCookie = context.HttpContext.Request.Cookies["XSRF-TOKEN"];
        
        if (string.IsNullOrWhiteSpace(xsrfTokenHeader) || xsrfTokenHeader != xsrfTokenCookie)
        {
            context.Result = new ForbidResult();
            return;
        }

        // 可选:验证XSRF Token是否和用户会话匹配(从Redis读取)
        var user = await GetUserFromAuthToken(authToken);
        if (user != null)
        {
            var storedXsrfToken = await _redisCache.GetStringAsync($"XSRF:{user.Id}");
            if (storedXsrfToken != xsrfTokenHeader)
            {
                context.Result = new ForbidResult();
                return;
            }
        }

        // 步骤3:用ASP.NET Identity验证Auth Token有效性
        if (user == null)
        {
            context.Result = new UnauthorizedResult();
            return;
        }

        var isValidToken = await _signInManager.VerifyUserTokenAsync(user, "Default", "AuthToken", authToken);
        if (!isValidToken)
        {
            context.Result = new UnauthorizedResult();
            return;
        }

        // 步骤4:将验证通过的用户身份注入请求上下文
        var userClaims = await _userManager.GetClaimsAsync(user);
        var identity = new ClaimsIdentity(userClaims, "CookieAuth");
        context.HttpContext.User = new ClaimsPrincipal(identity);
    }

    // 从Auth Token反向查找用户的逻辑
    private async Task<ApplicationUser> GetUserFromAuthToken(string authToken)
    {
        // 方式1:通过Identity的UserToken表查询(需要确保Token存储在表中)
        var userToken = await _userManager.Users
            .SelectMany(u => u.Tokens)
            .FirstOrDefaultAsync(t => t.Value == authToken 
                                     && t.LoginProvider == "Default" 
                                     && t.Name == "AuthToken");

        return userToken != null ? await _userManager.FindByIdAsync(userToken.UserId) : null;

        // 方式2:如果生成Token时把用户ID加密在Token里,这里解密直接获取用户ID
        // var userId = DecryptUserIdFromToken(authToken);
        // return await _userManager.FindByIdAsync(userId);
    }
}

3. 注册筛选器并启用

在Program.cs里把自定义筛选器注册为服务,然后可以选择全局启用或者在特定控制器/方法上启用:

// 注册筛选器服务
builder.Services.AddScoped<CookieAuthTokenFilter>();

// 全局启用(所有控制器都会验证)
builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add<CookieAuthTokenFilter>();
});

// 或者在单个控制器上启用
[TypeFilter(typeof(CookieAuthTokenFilter))]
public class ProtectedController : Controller
{
    // ...
}
关键注意事项
  • Cookie属性配置:生产环境务必确保Secure=true,SameSite根据跨域需求选择Strict(同域)或Lax(轻微跨域),跨域场景下用None但必须配合Secure。
  • Token过期与刷新:可以在筛选器里检查Token的过期时间,快要过期时自动生成新Token并更新Cookie,避免用户频繁登录。
  • XSRF验证增强:把XSRF Token和用户会话绑定存储(比如Redis),能有效防范更复杂的CSRF攻击,不要只依赖Cookie和Header的简单对比。
  • 错误状态码:验证失败时返回正确的HTTP状态码,401表示未授权(没有Token),403表示禁止访问(Token无效或XSRF验证失败)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 07:22:03