在MVC控制器中通过Refresh Token获取过期Access Token的方法
在MVC控制器中通过Refresh Token刷新Access Token
一、后端Refresh Token接口实现(修正版)
原代码存在未定义userEmail、逻辑漏洞等问题,以下是完善后的可运行实现:
// 建议用POST请求,避免Refresh Token暴露在URL中 [Route("api/account/RefreshToken")] [HttpPost] public IActionResult RefreshToken([FromBody] RefreshTokenRequest request) { // 从数据库查询对应Refresh Token记录 var refreshTokenRecord = _context.UserRefreshTokens.FirstOrDefault(a => a.Token == request.RefreshToken); // 情况1:Refresh Token不存在,返回无效提示 if (refreshTokenRecord == null) { return BadRequest("无效的Refresh Token"); } // 情况2:Refresh Token已过期,清理过期记录并返回提示 if (refreshTokenRecord.Expire < DateTime.Now) { _context.UserRefreshTokens.Remove(refreshTokenRecord); _context.SaveChanges(); return BadRequest("Refresh Token已过期,请重新登录"); } // 情况3:Refresh Token有效,关联用户并生成新Token var user = _context.Users.FirstOrDefault(a => a.Id == refreshTokenRecord.UserId); if (user == null) { return BadRequest("关联用户不存在"); } // 生成新的Access Token(实际项目中不要用明文密码,用用户标识生成) var newAccessToken = GenerateToken(new UserForLogin { UserEmail = user.UserEmail, Password = user.Password }); // 生成新的Refresh Token并更新数据库记录 var newRefreshToken = GenerateRefreshToken(); refreshTokenRecord.Token = newRefreshToken.Token; refreshTokenRecord.Expire = newRefreshToken.Expire; _context.SaveChanges(); return Ok(new TokenResponse { AccessToken = newAccessToken, RefreshToken = newRefreshToken.Token }); } // 请求参数模型 public class RefreshTokenRequest { public string RefreshToken { get; set; } } // 返回结果模型 public class TokenResponse { public string AccessToken { get; set; } public string RefreshToken { get; set; } }
核心优化点
- 改用
POST请求提升安全性 - 通过Refresh Token记录关联用户,避免使用未定义的
userEmail - 过期Refresh Token自动从数据库清理
- 生成新Refresh Token替换旧值,防止重复滥用
二、在MVC中调用Refresh Token接口
1. 全局拦截自动刷新(推荐)
自定义请求拦截器,遇到Token过期(401状态码)时自动调用刷新接口:
public class TokenRefreshHandler : DelegatingHandler { private readonly IHttpContextAccessor _httpContextAccessor; public TokenRefreshHandler(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var response = await base.SendAsync(request, cancellationToken); // 检测Token过期 if (response.StatusCode == HttpStatusCode.Unauthorized) { var httpContext = _httpContextAccessor.HttpContext; var refreshToken = httpContext.Session.GetString("RefreshToken"); if (!string.IsNullOrEmpty(refreshToken)) { // 调用刷新接口 var refreshResponse = await new HttpClient().PostAsJsonAsync( "https://你的域名/api/account/RefreshToken", new RefreshTokenRequest { RefreshToken = refreshToken } ); if (refreshResponse.IsSuccessStatusCode) { var tokenResp = await refreshResponse.Content.ReadFromJsonAsync<TokenResponse>(); // 更新会话中的Token httpContext.Session.SetString("AccessToken", tokenResp.AccessToken); httpContext.Session.SetString("RefreshToken", tokenResp.RefreshToken); // 重新发起原请求 request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenResp.AccessToken); response = await base.SendAsync(request, cancellationToken); } } } return response; } }
2. 前端手动调用(Ajax示例)
在页面请求中捕获401错误,手动触发刷新:
function fetchData() { $.ajax({ url: "/api/data", headers: { "Authorization": "Bearer " + localStorage.getItem("AccessToken") }, error: function(xhr) { if (xhr.status === 401) { // 调用刷新接口 $.post("/api/account/RefreshToken", { RefreshToken: localStorage.getItem("RefreshToken") }, function(tokenResp) { // 更新本地存储的Token localStorage.setItem("AccessToken", tokenResp.AccessToken); localStorage.setItem("RefreshToken", tokenResp.RefreshToken); // 重新发起请求 fetchData(); } ).fail(function() { // Refresh Token失效,跳转登录页 window.location.href = "/Account/Login"; }); } } }); }
三、关键注意事项
- 存储安全:Refresh Token优先存在服务器
Session中,客户端存储建议用HttpOnlyCookie - 有效期设置:Access Token设15分钟以内,Refresh Token设7天左右
- 回收机制:用户注销、修改密码时,立即删除对应Refresh Token记录
- 强制HTTPS:所有Token相关请求必须用HTTPS传输,防止窃取
内容的提问来源于stack exchange,提问作者Bhupendra Chauhan
相关产品推荐
相关产品推荐

