【问题标题】:Secure File Download Using Blazor Webassembly and ASP.NET Core使用 Blazor Webassembly 和 ASP.NET Core 安全文件下载
【发布时间】:2020-12-05 16:40:06
【问题描述】:

我在这里找到了一个解决方案,展示了如何使用 JS 注入创建控制器和下载文件: How can one generate and save a file client side using Blazor?

但是,将 [Authorize] 属性添加到控制器会阻止任何下载文件的尝试(即使已登录)。我希望只有获得授权的人才能访问下载文件。

网站的其余部分使用 JWT 没有问题。

我的问题是如何将 JWT 身份验证添加到此文件下载功能?还是有替代方法?这些文件在服务器的文件系统中,上面的方法对内存非常友好,所以我更喜欢远离 blob。

注意:我使用的是应用内用户帐户。

【问题讨论】:

    标签: javascript c# asp.net-core blazor blazor-webassembly


    【解决方案1】:

    为了保护文件下载,我使用下载请求 URI 中发送的一次性令牌:

    1. 定义一个类来存储一次令牌
    public class OneTimeToken
    {
        public string Id { get; set; }
    
        public string ClientId { get; set; }
    
        public string UserId { get; set; }
    
        public string Data { get; set; }
    }
    

    我更喜欢将令牌存储在数据库中,但您可以选择将其存储在内存中,但显然是服务器端。

    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。将包 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 上下文,如果它在内存中,则可以使用对象缓存。

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

    【讨论】:

      猜你喜欢
      • 2021-09-22
      • 2021-04-14
      • 2023-03-17
      • 1970-01-01
      • 1970-01-01
      • 2022-11-09
      • 1970-01-01
      • 2011-02-03
      • 2020-10-12
      相关资源
      最近更新 更多