簡體   English   中英

使用 Blazor Webassembly 和 ASP.NET 內核安全文件下載

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

我設法在這里找到了一個解決方案,展示了如何創建 controller 並使用 JS 注入下載文件: How can one generate and save a file client side using Blazor?

但是,將 [Authorize] 屬性添加到 controller 會阻止任何下載文件的嘗試(即使已登錄)。 我希望授權的人只能訪問下載文件。

該網站的 rest 使用 JWT 沒有問題。

我的問題是如何將 JWT 身份驗證添加到此文件下載功能? 還是有替代方法? 這些文件在服務器的文件系統中,上面的方法對 memory 非常友好,所以我更喜歡遠離 blob。

注意:我使用的是應用內用戶帳戶。

為了保護文件下載,我使用下載請求 URI 中發送的一次性令牌:

  1. 定義一個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; }
}

我更喜歡將令牌存儲在數據庫中,但您可以選擇將其存儲在 memory 但顯然是服務器端。

  1. 在下載之前創建一個令牌

在這里,我使用調用 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. 當用戶單擊下載鏈接時創建下載 uri

這里我使用了一個按鈕,但它適用於任何 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. 從 URL 中檢索令牌

4.1。 將 package IdentityServer4.AccessTokenValidation添加到您的 API 項目中。

在 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. 定義一項服務以從 URI 讀取和使用一次性令牌

令牌不能重復使用,因此在每次請求時都將其刪除。
這里只是一個示例。 如果將令牌存儲在 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