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
相关产品推荐
相关产品推荐

