如何在啟用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. 我們通過模擬點擊這個虛擬的超鏈接,來完成文件下載的功能。
let anchor = document.createElement("a");
let file = 'https://www.example.com/api/getFiles/'+fileId;

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);
    });

這個方案有兩個缺點:

  1. 就是只有當文件流完全讀取到Blob對象中之后,才會觸發真正的文件下載。因此如果文件內容過大話,瀏覽器會有一個長時間的靜止,當文件流全部加載到Blob對象之后,才會觸發下載操作。所以這里可能需要自己添加一個Loading效果,給用戶一些提示。
  2. 並不是所有的瀏覽器都支持Blob對象,在一些老的瀏覽器中Blob對象是不被支持的。

使用ASP.NET Core中的Data Protection

在之前的博客中,我有講解過ASP.NET Core中的Data Protection功能, 我們可以使用Data Protection將一些敏感信息加密。所以這里我們可以將一個需要授權才能使用下載文件的Api, 替換成2個Api

  • 第一個Api是需要授權的,它主要負責查看文件ID是否存在,如果存在,就使用Data Protection, 將這個ID加密,並返回給前端,這個ID的加密時效設置為5秒。

  • 第二個Api是不需要授權的,允許匿名訪問。它接收前一個Api提供的加密ID, 如果ID可以解密成功,就返回這個ID對應的文件流。

第一個Api的實例代碼:

[HttpGet]
[Route("~/api/file_links/{fileId}")]
public IActionResult GetFileLink(Guid fileId)
{
	if (_files.Any(p => p.FileId == fileId))
	{
		var matchedFile = _files.First(p => p.FileId == fileId);

		return Content(this.protector.Protect(matchedFile.FileId.ToString(),
			TimeSpan.FromSeconds(5)));
	}

	return StatusCode(500);
}

第二個Api的實例代碼:

[HttpGet]
[AllowAnonymous]
[Route("~/api/raw_files/{id}")]
public IActionResult GetRawFile(string id)
{
    try
    {
        var rawId = Guid.Parse(this.protector.Unprotect(id));
        var matchedFile = _files.First(p => p.FileId == rawId);
        matchedFile.FileContent.Position = 0;

        return File(matchedFile.FileContent, "text/plain", "helloWorld.txt");
    }
    catch
    {
        return StatusCode(401);
    }
}

使用這種方式,雖然我們開放了一個未經授權就可以訪問的Api入口,但是由於使用了Data Protection, 所以對於非法的請求,系統也可以進行一定的屏蔽。

最終效果

針對以上2種下載方式,我創建了一個小項目,項目地址:https://github.com/lamondlu/Sample_DownloadFileInAuth, 打開之后頁面如下。

普通下載

由於缺少Token, 所以下載失敗,返回401

使用Blob下載

使用Blob下載之后,文件下載成功

使用Data Protection

使用Data Protection后,文件下載成功

總結

本文只算拋磚引玉,如果大家有更好的解決方案,歡迎一起討論。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM