简体   繁体   English

使用 Blazor Webassembly 和 ASP.NET 内核安全文件下载

[英]Secure File Download Using Blazor Webassembly and ASP.NET Core

I've managed to find a solution here that shows how to create a controller and download files using JS injection: How can one generate and save a file client side using Blazor?我设法在这里找到了一个解决方案,展示了如何创建 controller 并使用 JS 注入下载文件: How can one generate and save a file client side using Blazor?

However, adding the [Authorize] attribute to the controller blocks any attempts (even if logged in) to download the file.但是,将 [Authorize] 属性添加到 controller 会阻止任何下载文件的尝试(即使已登录)。 I want authorized people only to have access to download files.我希望授权的人只能访问下载文件。

The rest of the website is using JWT without issues.该网站的 rest 使用 JWT 没有问题。

My question is how do I add JWT authentication to this file download feature?我的问题是如何将 JWT 身份验证添加到此文件下载功能? Or is there an alternative way?还是有替代方法? The files are in the file system of the server and the approach above is very kind to the memory so I prefer to stay away from blobs.这些文件在服务器的文件系统中,上面的方法对 memory 非常友好,所以我更喜欢远离 blob。

Note: I'm using in-application user accounts.注意:我使用的是应用内用户帐户。

To secure a file download I use a one time token sent in the download request URI:为了保护文件下载,我使用下载请求 URI 中发送的一次性令牌:

  1. Define a class to store one time toke定义一个class来存储一次toke
public class OneTimeToken
{
    public string Id { get; set; }

    public string ClientId { get; set; }

    public string UserId { get; set; }

    public string Data { get; set; }
}

I prefer to store tokens in DB but you can choose to store it in memory but server side obviously.我更喜欢将令牌存储在数据库中,但您可以选择将其存储在 memory 但显然是服务器端。

  1. Before download create a token在下载之前创建一个令牌

Here I use a service calling an API to create my token在这里,我使用调用 API 的服务来创建我的令牌

public class OneTimeTokenService
{
    private readonly IAdminStore<OneTimeToken> _store; // this my service calling the API
    private readonly AuthenticationStateProvider _stateProvider;
    private readonly IAccessTokenProvider _provider;
    private readonly IOptions<RemoteAuthenticationOptions<OidcProviderOptions>> _options;

    public OneTimeTokenService(IAdminStore<OneTimeToken> store,
        AuthenticationStateProvider state,
        IAccessTokenProvider provider,
        IOptions<RemoteAuthenticationOptions<OidcProviderOptions>> options)
    {
        _store = store ?? throw new ArgumentNullException(nameof(store));
        _stateProvider = state ?? throw new ArgumentNullException(nameof(state));
        _provider = provider ?? throw new ArgumentNullException(nameof(provider));
        _options = options ?? throw new ArgumentNullException(nameof(options));
    }

    public async Task<string> GetOneTimeToken()
    {
        // gets the user access token
        var tokenResult = await _provider.RequestAccessToken().ConfigureAwait(false);
        tokenResult.TryGetToken(out AccessToken token);
        // gets the authentication state
        var state = await _stateProvider.GetAuthenticationStateAsync().ConfigureAwait(false);
        // creates a one time token
        var oneTimeToken = await _store.CreateAsync(new OneTimeToken
        {
            ClientId = _options.Value.ProviderOptions.ClientId,
            UserId = state.User.Claims.First(c => c.Type == "sub").Value,
            Expiration = DateTime.UtcNow.AddMinutes(1),
            Data = token.Value
        }).ConfigureAwait(false);

        return oneTimeToken.Id;
    }
}
  1. Create the download uri when the user click the download link当用户单击下载链接时创建下载 uri

Here I use a button, but it work with a any html element, you can use a link instead.这里我使用了一个按钮,但它适用于任何 html 元素,您可以使用链接代替。

@inject OneTimeTokenService _service
<button class="btn btn-secondary" @onclick="Download" >
    <span class="oi oi-arrow-circle-top"></span><span class="sr-only">Download 
    </span>
</button>
@code {
    private async Task Download()
    {
        var token = await _service.GetOneTimeToken().ConfigureAwait(false);
        var url = $"http://locahost/stuff?otk={token}";
        await _jsRuntime.InvokeVoidAsync("open", url, "_blank").ConfigureAwait(false);
    }
}
  1. Retrieve the token from the URL从 URL 中检索令牌

4.1. 4.1。 Add the package IdentityServer4.AccessTokenValidation to your API project.将 package IdentityServer4.AccessTokenValidation添加到您的 API 项目中。

In Startup ConfigureServices method use the IdentityServer authentication:在 Startup ConfigureServices 方法中使用 IdentityServer 身份验证:

services.AddTransient<OneTimeTokenService>()
    .AddAuthentication()
    .AddIdentityServerAuthentication(options =>
    {
        options.TokenRetriever = request =>
        {
            var oneTimeToken = TokenRetrieval.FromQueryString("otk")(request);
            if (!string.IsNullOrEmpty(oneTimeToken))
            {
                return request.HttpContext
                    .RequestServices
                    .GetRequiredService<OneTimeTokenService>()
                    .GetOneTimeToken(oneTimeToken);
            }
            return TokenRetrieval.FromAuthorizationHeader()(request);
        };
    });
  1. Define a service to read and consume the one time token from the URI定义一项服务以从 URI 读取和使用一次性令牌

The token must not be reusable, so it's delete on each request.令牌不能重复使用,因此在每次请求时都将其删除。
Here it's just a sample.这里只是一个示例。 If you store tokens in DB you can use an EF context, if it's in memory, you can use an object cache for exemple.如果将令牌存储在 DB 中,则可以使用 EF 上下文,如果它在 memory 中,则可以使用 object 缓存为例。

public class OneTimeTokenService{
    private readonly IAdminStore<OneTimeToken> _store;

    public OneTimeTokenService(IAdminStore<OneTimeToken> store)
    {
        _store = store ?? throw new ArgumentNullException(nameof(store));
    }

    public string GetOneTimeToken(string id)
    {
        // gets the token.
        var token = _store.GetAsync(id, new GetRequest()).GetAwaiter().GetResult();
        if (token == null)
        {
            return null;
        }
        // deletes the token to not reuse it.
        _store.DeleteAsync(id).GetAwaiter().GetResult();
        return token.Data;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM