Vue.js与ASP.NET Core应用LTI认证后JWT存储及鉴权问题
问题分析与解决方案
问题概述
基于Vue.js客户端+ASP.NET Core 9后端搭建的系统,需通过LTI认证被第三方平台调用,对方仅首次请求时发送鉴权JWT令牌。当前配置存在以下问题:
- 在第三方平台
https://saltire.lti.app/platform的iframe中加载客户端,无法读取后端设置的JWT Cookie; - 新标签页加载时同样看不到Cookie,
/vue/api/me接口鉴权失败; - 浏览器控制台提示“Cookie “jwt” has been rejected for invalid domain”,手动指定公网或localhost域名均无效。
后端当前通过OnMessageReceived事件从表单提取JWT并设置HttpOnly Cookie,LTI流程为第三方POST到/launch接口,验证通过后通过RedirectPreserveMethod("/")重定向到客户端。本地用ngrok暴露公网地址https://15e1-93-41-196-8.ngrok-free.app,后端运行在https://localhost:7093,Vue应用在https://localhost:64928。
可行解决方案
方案1:修复Cookie域名配置
Cookie被拒绝的核心原因是域名不匹配,浏览器不允许跨域名设置Cookie。需动态适配请求域名,或开发环境不指定域名让浏览器自动匹配。
修改OnMessageReceived中的Cookie设置代码:
context.Response.Cookies.Append("jwt", token, new CookieOptions { HttpOnly = true, Secure = true, SameSite = SameSiteMode.None, // 开发环境不指定域名,生产环境自动匹配当前请求域名 Domain = Environment.IsDevelopment() ? null : new Uri(context.Request.Host.Value).Host });
额外注意:
- 后端与Vue应用需部署在同一域名/子域名下,否则Cookie无法跨域共享;
- ngrok测试时,所有请求需通过ngrok公网地址访问,避免localhost与ngrok域名混用。
方案2:前端存储JWT并在请求头携带
放弃Cookie方案,将JWT返回给前端存储,每次请求接口时通过请求头传递令牌。
- 修改
/launch接口,返回内嵌脚本将JWT存入前端存储:
[HttpPost("launch")] [Authorize] [Consumes("application/x-www-form-urlencoded")] public async Task<IActionResult> PostLaunch([FromForm] JwtPayload request, HttpContext context) { await Task.CompletedTask; if (HttpContext.User.Identity?.IsAuthenticated == true) { var token = context.Request.Form[jwtSection.TokenField]; // 返回跳转页面,将token存入localStorage后跳转到客户端 return Content($@" <script> localStorage.setItem('lti_jwt', '{token}'); window.location.href = '/'; </script> ", "text/html"); } return Unauthorized(); }
- 在Vue应用中添加请求拦截器,自动携带令牌:
// 以axios为例 axios.interceptors.request.use(config => { const token = localStorage.getItem('lti_jwt'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; });
后端JWTBearer默认支持从请求头读取令牌,无需额外修改配置。
方案3:使用ASP.NET Core Session管理身份
利用Session替代手动Cookie管理,由框架自动维护会话凭证。
- 配置Session服务:
// Program.cs中添加 builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromHours(1); options.Cookie.HttpOnly = true; options.Cookie.Secure = CookieSecurePolicy.Always; options.Cookie.SameSite = SameSiteMode.None; });
- 启用Session中间件(需放在
UseAuthorization之前):
app.UseSession();
- 修改
/launch接口存储用户信息到Session:
[HttpPost("launch")] [Authorize] [Consumes("application/x-www-form-urlencoded")] public async Task<IActionResult> PostLaunch([FromForm] JwtPayload request) { await Task.CompletedTask; if (HttpContext.User.Identity?.IsAuthenticated == true) { var userName = HttpContext.User.Claims.FirstOrDefault(f => f.Type == "name")?.Value ?? ""; HttpContext.Session.SetString("UserName", userName); return RedirectPreserveMethod("/"); } return Unauthorized(); }
- 修改
/vue/api/me接口从Session读取信息:
app.MapGet("vue/api/me", (HttpContext context) => { var userName = context.Session.GetString("UserName"); return new { name = userName }; }).RequireAuthorization();
关键注意事项
- iframe/跨域场景下,
SameSite=None必须配合Secure=true,且浏览器需支持该配置; - 若前后端跨域,需配置CORS允许携带凭证(添加
.WithCredentials()); - ngrok测试时,确保所有请求统一使用ngrok公网地址,避免域名不一致导致的Cookie问题。
内容的提问来源于stack exchange,提问作者Daniele Tentoni
相关产品推荐
相关产品推荐

