下载文件时如何传递身份验证令牌? [英] How can I pass an auth token when downloading a file?

查看:75
本文介绍了下载文件时如何传递身份验证令牌?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个Web应用程序,其中Angular(7)前端与服务器上的REST API通信,并使用OpenId Connect(OIDC)进行身份验证.我使用的是 HttpInterceptor ,它向我的HTTP请求添加了 Authorization 标头,以向服务器提供auth令牌.到目前为止,一切都很好.

I have a web app where the Angular (7) front-end communicates with a REST API on the server, and uses OpenId Connect (OIDC) for authentication. I'm using an HttpInterceptor that adds an Authorization header to my HTTP requests to provide the auth token to the server. So far, so good.

但是,除了传统的JSON数据外,我的后端还负责即时生成文档.在添加身份验证之前,我可以简单地链接到这些文档,如下所示:

However, as well as traditional JSON data, my back-end is also responsible for generating documents on-the-fly. Before I added authentication, I could simply link to these documents, as in:

<a href="https://my-server.com/my-api/document?id=3">Download</a>

但是,现在我添加了身份验证,此操作不再起作用,因为在获取文档时浏览器未在请求中包含auth令牌-因此,我得到了 401-Unathorized 服务器的响应.

However, now that I've added authentication, this no longer works, because the browser does not include the auth token in the request when fetching the document - and so I get a 401-Unathorized response from the server.

因此,我不能再依赖原始的HTML链接-我需要创建自己的HTTP请求,并显式添加auth令牌.但是,如何确保用户体验与用户单击链接相同?理想情况下,我希望使用服务器建议的文件名而不是通用文件名来保存文件.

So, I can no longer rely on a vanilla HTML link - I need to create my own HTTP request, and add the auth token explicitly. But then, how can I ensure that the user experience is the same as if the user had clicked a link? Ideally, I'd like the file to be saved with the filename suggested by the server, rather than a generic filename.

推荐答案

我将部分基于 content-disposition 标头来确定文件名应该是什么).

I've cobbled together something that "works on my machine" based partly on this answer and others like it - though my effort is "Angular-ized" by being packaged as a re-usable directive. There's not much to it (most of the code is doing the grunt-work of figuring out what the filename should be based on the content-disposition header sent by the server).

download-file.directive.ts :

import { Directive, HostListener, Input } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';

@Directive({
  selector: '[downloadFile]'
})
export class DownloadFileDirective {
  constructor(private readonly httpClient: HttpClient) {}

  private downloadUrl: string;

  @Input('downloadFile')
  public set url(url: string) {
    this.downloadUrl = url;
  };

  @HostListener('click')
  public async onClick(): Promise<void> {

    // Download the document as a blob
    const response = await this.httpClient.get(
      this.downloadUrl,
      { responseType: 'blob', observe: 'response' }
    ).toPromise();

    // Create a URL for the blob
    const url = URL.createObjectURL(response.body);

    // Create an anchor element to "point" to it
    const anchor = document.createElement('a');
    anchor.href = url;

    // Get the suggested filename for the file from the response headers
    anchor.download = this.getFilenameFromHeaders(response.headers) || 'file';

    // Simulate a click on our anchor element
    anchor.click();

    // Discard the object data
    URL.revokeObjectURL(url);
  }

  private getFilenameFromHeaders(headers: HttpHeaders) {
    // The content-disposition header should include a suggested filename for the file
    const contentDisposition = headers.get('Content-Disposition');
    if (!contentDisposition) {
      return null;
    }

    /* StackOverflow is full of RegEx-es for parsing the content-disposition header,
    * but that's overkill for my purposes, since I have a known back-end with
    * predictable behaviour. I can afford to assume that the content-disposition
    * header looks like the example in the docs
    * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
    *
    * In other words, it'll be something like this:
    *    Content-Disposition: attachment; filename="filename.ext"
    *
    * I probably should allow for single and double quotes (or no quotes) around
    * the filename. I don't need to worry about character-encoding since all of
    * the filenames I generate on the server side should be vanilla ASCII.
    */

    const leadIn = 'filename=';
    const start = contentDisposition.search(leadIn);
    if (start < 0) {
      return null;
    }

    // Get the 'value' after the filename= part (which may be enclosed in quotes)
    const value = contentDisposition.substring(start + leadIn.length).trim();
    if (value.length === 0) {
      return null;
    }

    // If it's not quoted, we can return the whole thing
    const firstCharacter = value[0];
    if (firstCharacter !== '\"' && firstCharacter !== '\'') {
      return value;
    }

    // If it's quoted, it must have a matching end-quote
    if (value.length < 2) {
      return null;
    }

    // The end-quote must match the opening quote
    const lastCharacter = value[value.length - 1];
    if (lastCharacter !== firstCharacter) {
      return null;
    }

    // Return the content of the quotes
    return value.substring(1, value.length - 1);
  }
}

它的用法如下:

<a downloadFile="https://my-server.com/my-api/document?id=3">Download</a>

...或者,当然:

<a [downloadFile]="myUrlProperty">Download</a>

请注意,我没有在此代码中将auth令牌显式添加到HTTP请求中,因为我的已经将 all HttpClient 调用用于此操作> HttpInterceptor 实现(未显示).要在没有拦截器的情况下执行此操作,仅是在请求中添加标头(在我的示例中为 Authorization 标头).

Note that I'm not explicitly adding the auth token to the HTTP request in this code, because that's already taken care of for all HttpClient calls by my HttpInterceptor implementation (not shown). To do this without an interceptor is simply a case of adding a header to the request (in my case, an Authorization header).

还有一件值得一提的事情是,如果所调用的Web API在使用CORS的服务器上,则可能会阻止客户端代码访问内容处置响应标头.要允许访问此标头,您可以让服务器发送相应的 access-control-allow-headers 标头.

One more thing worth mentioning, is that, if the web API being called is on a server that uses CORS, it might prevent the client-side code from accessing the content-disposition response header. To allow access to this header, you can have the server send an appropriate access-control-allow-headers header.

这篇关于下载文件时如何传递身份验证令牌?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆