javascript 如何使用基于JWT的身份验证处理文件下载?

67up9zun  于 2023-02-07  发布在  Java
关注(0)|答案(5)|浏览(235)

我正在用Angular编写一个webapp,其中身份验证由JWT令牌处理,这意味着每个请求都有一个“Authentication”头,其中包含所有必要的信息。
这对于REST调用很有效,但是我不明白应该如何处理后端托管的文件的下载链接(这些文件驻留在托管Web服务的同一台服务器上)。
我不能使用常规的<a href='...'/>链接,因为它们不会携带任何头,认证也会失败。
我想到的一些解决方案:
1.在服务器上生成临时的不安全下载链接
1.将身份验证信息作为url参数传递,然后手动处理
1.通过XHR获取数据并保存到文件客户端。
以上所有这些都不太令人满意。
1是我现在使用的解决方案。我不喜欢它有两个原因:首先,它在安全性方面并不理想,其次,它可以工作,但需要做大量工作,尤其是在服务器上:要下载一些东西,我需要调用一个服务来生成一个新的“随机”url,将其存储在某个地方(可能在DB上)一段时间,然后将其返回给客户端。2客户端获得url,并使用window.open或类似的方法。3当请求时,新的url应该检查它是否仍然有效,然后返回数据。
2似乎至少有同样多的工作。
3看起来工作量很大,即使使用可用的库,也有很多潜在的问题。(我需要提供自己的下载状态栏,在内存中加载整个文件,然后要求用户在本地保存文件)。
这个任务看起来是一个非常基本的任务,所以我想知道是否有更简单的东西可以使用。
我不一定要寻找一个解决方案“Angular 的方式”。常规的Javascript将是好的。

t2a7ltrp

t2a7ltrp1#

下面是使用download属性、fetch API和URL.createObjectURL在客户机上下载该文件的一种方法:使用JWT获取文件,将有效负载转换为blob,将blob放入objectURL,将锚标记的源设置为该objectURL,然后在javascript中单击该objectURL。

let anchor = document.createElement("a");
document.body.appendChild(anchor);
let file = 'https://www.example.com/some-file.pdf';

let headers = new Headers();
headers.append('Authorization', 'Bearer MY-TOKEN');

fetch(file, { headers })
    .then(response => response.blob())
    .then(blobby => {
        let objectUrl = window.URL.createObjectURL(blobby);

        anchor.href = objectUrl;
        anchor.download = 'some-file.pdf';
        anchor.click();

        window.URL.revokeObjectURL(objectUrl);
    });

download属性的值将是最终的文件名。如果需要,您可以从内容处置响应头as described in other answers中挖掘预期的文件名。

a0x5cqrl

a0x5cqrl2#

技术

基于Auth0的Matias Woloski(著名的JWT传道者)的this advice,我通过使用Hawk生成一个签名请求来解决这个问题。
引用Woloski的话:
解决这个问题的方法是像AWS那样生成一个签名请求。
此技术的Here you have an example,用于激活链接。

后端

我创建了一个API来签署我的下载网址:
请求:

POST /api/sign
Content-Type: application/json
Authorization: Bearer...
{"url": "https://path.to/protected.file"}

回复:

{"url": "https://path.to/protected.file?bewit=NTUzMDYzZTQ2NDYxNzQwMGFlMDMwMDAwXDE0NTU2MzU5OThcZDBIeEplRHJLVVFRWTY0OWFFZUVEaGpMOWJlVTk2czA0cmN6UU4zZndTOD1c"}

有了签名的URL,我们就可以得到文件
请求:

GET https://path.to/protected.file?bewit=NTUzMDYzZTQ2NDYxNzQwMGFlMDMwMDAwXDE0NTU2MzU5OThcZDBIeEplRHJLVVFRWTY0OWFFZUVEaGpMOWJlVTk2czA0cmN6UU4zZndTOD1c

回复:

Content-Type: multipart/mixed; charset="UTF-8"
Content-Disposition': attachment; filename=protected.file
{BLOB}

前端(通过jojoyuji

这样,您只需单击一次即可完成所有操作:

function clickedOnDownloadButton() {

  postToSignWithAuthorizationHeader({
    url: 'https://path.to/protected.file'
  }).then(function(signed) {
    window.location = signed.url;
  });

}
798qvoo8

798qvoo83#

一个替代现有的“fetch/createObjectURL”和“download-token”方法是一个标准的Form POST,目标是一个新窗口。一旦浏览器读取了服务器响应中的附件标题,它将关闭新标签并开始下载。同样的方法也适用于在新标签中显示PDF等资源。
这对旧的浏览器有更好的支持,避免了管理一种新的令牌类型。这也比URL上的基本认证有更好的长期支持,因为浏览器正在删除URL上的用户名/密码支持。
在 * 客户端 ,我们使用target="_blank"来避免导航,即使在失败的情况下,这对SPA(单页面应用程序)特别重要。
主要的警告是,
服务器端 * JWT验证必须从POST数据中获取标记,而不是从头部中获取标记。如果您的框架使用Authentication头部自动管理对路由处理程序的访问,则可能需要将处理程序标记为未验证/匿名,以便您可以手动验证JWT以确保正确的授权。
表单可以动态创建并立即销毁,以便正确清理(注意:这可以在普通JS中完成,但为了清楚起见,这里使用JQuery)-

function DownloadWithJwtViaFormPost(url, id, token) {
    var jwtInput = $('<input type="hidden" name="jwtToken">').val(token);
    var idInput = $('<input type="hidden" name="id">').val(id);
    $('<form method="post" target="_blank"></form>')
                .attr("action", url)
                .append(jwtInput)
                .append(idInput)
                .appendTo('body')
                .submit()
                .remove();
}

只需添加任何需要作为隐藏输入提交的额外数据,并确保它们已追加到表单中。

g2ieeal7

g2ieeal74#

詹姆斯答案的纯JS版本

function downloadFile (url, token) {
    let form = document.createElement('form')
    form.method = 'post'
    form.target = '_blank'
    form.action = url
    form.innerHTML = '<input type="hidden" name="jwtToken" value="' + token + '">'

    console.log('form:', form)

    document.body.appendChild(form)
    form.submit()
    document.body.removeChild(form)
}
5cg8jx4n

5cg8jx4n5#

我会生成用于下载的令牌。
在angular中,发出一个经过验证的请求来获取一个临时令牌(比如一个小时),然后将其作为get参数添加到url中,这样你就可以用任何你喜欢的方式下载文件(window.open ...)

相关问题