如何在启用JWT Token授权的.NET Core WebApi项目中下载文件

  • A+
所属分类:.NetCore

如何在启用JWT Token授权的.NET Core WebApi项目中下载文件

背景

前几天,做项目的时候遇到一个文件下载的问题。当前系统是一个前后端分离的项目,前端是一个AngularJs项目, 后端是一个.NET Core WebApi项目。后端的Api项目使用了Jwt Token授权,所以每个Api请求都需要传递一个Bearer Token。

这一切都看起来理所当然,但是当需要从WebApi下载文件的时候,出现了问题。以前下载文件的时候,我们可以在Javascript中使用window.open('[文件下载Api]')的方式下载文件,但是这个方法不能接收Bearer Token, 所以就会导致文件下载失败,返回一个401未授权的响应码。

可能有的同学会将这个文件下载Api设置成允许匿名访问,但是这样会导致系统不安全。

解决方案

使用Blob对象

Blob对象可以看做是Javascript中的二进制容器, 它可以存储文件的二进制流。所以我们可以通过如下思路完成文件下载:

  1. 创建一个异步请求来下载文件的二进制流,这个请求的头部需要附加Bearer Token,在方法回调中,我们将文件二进制流保存在一个Blob对象中  
  2. 我们使用Javascript添加一个虚拟的超链接,超链接的href属性指向了刚刚的Blob对象。  
  3. 我们通过模拟点击这个虚拟的超链接,来完成文件下载的功能。  
  1. let anchor = document.createElement("a");  
  2. let file = 'https://www.example.com/api/getFiles/'+fileId;  
  3.   
  4. let headers = new Headers();  
  5. headers.append('Authorization', 'Bearer MY-TOKEN');  
  6.   
  7. fetch(file, { headers })  
  8.     .then(response => response.blob())  
  9.     .then(blobby => {  
  10.         let objectUrl = window.URL.createObjectURL(blobby);  
  11.   
  12.         anchor.href = objectUrl;  
  13.         anchor.download = 'some-file.pdf';  
  14.         anchor.click();  
  15.   
  16.         window.URL.revokeObjectURL(objectUrl);  
  17.     });  

这个方案有两个缺点:

  1. 就是只有当文件流完全读取到Blob对象中之后,才会触发真正的文件下载。因此如果文件内容过大话,浏览器会有一个长时间的静止,当文件流全部加载到Blob对象之后,才会触发下载操作。所以这里可能需要自己添加一个Loading效果,给用户一些提示。  
  2. 并不是所有的浏览器都支持Blob对象,在一些老的浏览器中Blob对象是不被支持的。  

使用ASP.NET Core中的Data Protection

我们可以使用Data Protection将一些敏感信息加密。所以这里我们可以将一个需要授权才能使用下载文件的Api, 替换成2个Api

  1. 第一个Api是需要授权的,它主要负责查看文件ID是否存在,如果存在,就使用Data Protection, 将这个ID加密,并返回给前端,这个ID的加密时效设置为5秒。  
  2. 第二个Api是不需要授权的,允许匿名访问。它接收前一个Api提供的加密ID, 如果ID可以解密成功,就返回这个ID对应的文件流。  

第一个Api的实例代码:

  1. [HttpGet]  
  2. [Route("~/api/file_links/{fileId}")]  
  3. public IActionResult GetFileLink(Guid fileId)  
  4. {  
  5.     if (_files.Any(p => p.FileId == fileId))  
  6.     {  
  7.         var matchedFile = _files.First(p => p.FileId == fileId);  
  8.   
  9.         return Content(this.protector.Protect(matchedFile.FileId.ToString(),  
  10.             TimeSpan.FromSeconds(5)));  
  11.     }  
  12.   
  13.     return StatusCode(500);  
  14. }  

第二个Api的实例代码:

  1. [HttpGet]  
  2. [AllowAnonymous]  
  3. [Route("~/api/raw_files/{id}")]  
  4. public IActionResult GetRawFile(string id)  
  5. {  
  6.     try  
  7.     {  
  8.         var rawId = Guid.Parse(this.protector.Unprotect(id));  
  9.         var matchedFile = _files.First(p => p.FileId == rawId);  
  10.         matchedFile.FileContent.Position = 0;  
  11.   
  12.         return File(matchedFile.FileContent, "text/plain""helloWorld.txt");  
  13.     }  
  14.     catch  
  15.     {  
  16.         return StatusCode(401);  
  17.     }  
  18. }  

使用这种方式,虽然我们开放了一个未经授权就可以访问的Api入口,但是由于使用了Data Protection, 所以对于非法的请求,系统也可以进行一定的屏蔽。

钰玺

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: