ASP.NET Core 8中如何避免RefreshToken随每次请求自动发送?
你的核心问题是RefreshToken的Cookie路径设置为"/",导致所有请求都会自动携带它,这确实存在安全风险——RefreshToken仅需在AccessToken过期时调用刷新接口使用,无需每次请求都暴露。以下是两种解决方案,优先推荐第一种:
方案一:限制RefreshToken Cookie的生效路径(推荐,安全性更高)
通过修改RefreshToken的Cookie Path 属性,仅让它在刷新AccessToken的专属接口路径下生效,这样只有调用刷新接口时才会携带RefreshToken,其他请求不会自动发送。
1. 修改Login接口的RefreshToken Cookie配置
将RefreshToken的Cookie路径改为你的刷新接口路径(假设刷新接口为 /Api/User/Auth/Refresh),同时纠正过期时间的错误(配置项是小时,应该用AddHours而非AddMinutes):
HttpContext.Response.Cookies.Append( "RefreshToken", result.RefreshToken, new CookieOptions { Expires = DateTime.Now.AddHours( int.Parse(_configuration["JWT:RefTknValidityInHours"]) ), SameSite = SameSiteMode.Lax, HttpOnly = true, Path = "/Api/User/Auth/Refresh", // 仅在刷新接口路径下生效 Domain = ".localhost", } );
2. 调整JWT认证的消息接收逻辑
仅在处理刷新接口时,从Cookie中读取RefreshToken;其他正常请求读取AccessToken:
options.Events = new JwtBearerEvents { OnMessageReceived = context => { // 仅当请求刷新Token接口时,读取RefreshToken if (context.Request.Path.StartsWithSegments("/Api/User/Auth/Refresh")) { context.Token = context.Request.Cookies["RefreshToken"]; } else { // 普通业务请求读取AccessToken context.Token = context.Request.Cookies["AccessToken"]; } return Task.CompletedTask; } };
这种方案的优势是保持RefreshToken在HttpOnly Cookie中,能有效防范XSS攻击窃取Token,安全性远高于前端存储方案。
方案二:前端存储RefreshToken并手动按需发送(不推荐,存在XSS风险)
如果因业务特殊需求必须由前端控制RefreshToken的发送时机,可以将RefreshToken返回给前端,存入localStorage,在需要刷新AccessToken时手动携带。但此方案下RefreshToken容易被XSS攻击窃取,需谨慎使用。
1. 修改Login接口返回RefreshToken
将Login接口的请求方法从HEAD改为POST(HEAD请求无响应体),并返回RefreshToken:
[HttpPost("Auth/Login")] public async Task<IResult> Login([FromHeader] long phoneNumber) { AuthTokenDTO result = await _dBContext.User.Login(phoneNumber); if (result is null) return Results.BadRequest(); // 仅设置AccessToken为Cookie HttpContext.Response.Cookies.Append( "AccessToken", result.Token, new CookieOptions { Expires = DateTime.Now.AddMinutes( int.Parse(_configuration["JWT:TknValidityInMunutes"]) ), SameSite = SameSiteMode.Lax, HttpOnly = true, Path = "/", Domain = ".localhost", } ); // 将RefreshToken作为响应体返回给前端 return Results.Ok(new { RefreshToken = result.RefreshToken }); }
2. 前端存储并手动发送RefreshToken
登录时将RefreshToken存入localStorage,刷新AccessToken时手动携带:
// 登录请求 const loginResponse = await fetch('http://localhost:5247/Api/User/Auth/Login', { method: 'POST', credentials: 'include', headers: { phoneNumber, 'Content-Type': 'application/json' } }); const loginData = await loginResponse.json(); localStorage.setItem('refreshToken', loginData.RefreshToken); // 刷新AccessToken请求 const refreshToken = localStorage.getItem('refreshToken'); const refreshResponse = await fetch('http://localhost:5247/Api/User/Auth/Refresh', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${refreshToken}` } });
3. 后端刷新接口读取RefreshToken
在刷新接口中,从请求头的Authorization字段读取RefreshToken进行验证。
内容的提问来源于stack exchange,提问作者Reza245

