【问题标题】:File download in Angular and JWT在 Angular 和 JWT 中下载文件
【发布时间】:2026-02-04 05:25:01
【问题描述】:

TL;DR

如何使用 Angular 和 JWT 身份验证下载/保存文件而不在浏览器中留下令牌痕迹?


我的 Angular/Node 应用程序通过 HTTPS 保护并使用 JWT 进行身份验证。 JWT 存储在 sessionStorage 中,并在 Authorization 标头字段中传递给服务器的所有 AJAX 请求。

我需要应用程序中的功能来下载文件,以便浏览器自动保存文件(或显示保存位置的弹出窗口等)。

它应该可以在任何可以运行 Angular 的浏览器中完美运行。

我看过以下内容:

AJAX 请求。这不起作用,因为固有的安全措施阻止浏览器在本地保存文件。

在 Cookie 中传递 JWT - Cookie 是我想避免使用的东西,因此是使用 sessionStorage 的原因。

在查询字符串中传递 JWT,但这意味着它将记录在服务器日志中,更重要的是可以在浏览器历史记录中看到。

包含 POST 数据的表单的 iframe。无法使用此方法设置标题。

还有其他选择吗?

【问题讨论】:

标签: ajax angularjs node.js express jwt


【解决方案1】:

在网上搜索解决方案并没有找到任何令人满意的解决方案后,我终于找到了一个解决方案,该解决方案使用了来自各地的点点滴滴的建议。

希望这对寻找安全解决方案的人有所帮助。

我使用了 Bagofjuice 描述的 iframe 解决方案,但使用了 jquery.filedownload 插件(请参阅https://github.com/johnculviner/jquery.fileDownload)。

这是我为保护此请求所做的:

当用户单击下载按钮或链接时,我从服务器请求一个新令牌(为此创建了一个专用服务器 API 调用),我将其作为查询字符串附加到文件下载 url,然后使用 filedownload 插件下载文件。请注意,此令牌的过期时间非常短,因为它会立即用于下载文件,并且不应多次使用。

然后在服务器端,当令牌通过查询参数到达时,我提供响应并立即使令牌过期(除了过期时间之外,即使是最小的窗口也可以被利用)。

即时到期的技术可能会有所不同。我使用的是,当我创建这个临时令牌时,我将它存储在内存缓存中,并在第一次使用时将其从缓存中删除。如果当我通过请求接收它时它不存在于缓存中,我认为它是一个无效的令牌。这可以防止令牌通过查询参数被多次使用。因此,无论是记录在服务器端日志中还是在浏览器上留下痕迹都没有关系。对于网络农场,您可以轻松地将其存储在分布式缓存中。

【讨论】:

    【解决方案2】:

    iframe 方法很接近。只需将服务器设置为接受来自 POST 正文而不是查询字符串的 JWT。

    必须使用 iframe 不是最优雅的解决方案,但它似乎满足要求。这是我使用的指令:

    .directive('fileDownload', function () {
        return {
            restrict: 'A',
            replace: false,
            template: "<iframe style='position:fixed;display:none;top:-1px;left:-1px;' />",
            link: function (scope, element) {
                element.click(function() {
                    var iframe = element.find('iframe'),
                        iframeBody = $(iframe[0].contentWindow.document.body),
                    form = angular.element("<form method='POST' action='/my/endpoint/getFile'><input type='hidden' name='foo' value='bar' /><input type='hidden' name='access_token' value='" + scope.access_token + "' /></form>");
    
                    iframeBody.append(form);
                    form.submit();
                });
            }
        }
    });
    

    并且在 express 中间件中将 JWT 弹出到标头中,然后使用 express-jwt 模块对其进行验证

    if (req.body && req.body.hasOwnProperty('access_token')) {
        req.headers.authorization = 'Bearer ' + req.body.access_token;
    }
    

    【讨论】: