zoukankan      html  css  js  c++  java
  • 如何在启用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后,文件下载成功

    总结

    本文只算抛砖引玉,如果大家有更好的解决方案,欢迎一起讨论。

  • 相关阅读:
    解决shiro多次从redis读取session的问题
    软件测试其他方法
    异常HTTP Status 500
    支付
    java面试
    SQL入门
    软件测试理论基础
    软件测试学习第一章
    Linux在终端命令行模式下智能补全功能以及组合键
    Linux安装
  • 原文地址:https://www.cnblogs.com/lwqlun/p/10204918.html
Copyright © 2011-2022 走看看