使用 Blazor Webassembly 和 ASP.NET Core 安全下载文件
Secure File Download Using Blazor Webassembly and ASP.NET Core
我设法在这里找到了一个解决方案,展示了如何使用 JS 注入创建控制器和下载文件:
但是,将 [Authorize] 属性添加到控制器会阻止任何尝试(即使已登录)下载文件。我希望获得授权的人只能访问下载文件。
网站的其余部分正在使用 JWT,没有任何问题。
我的问题是如何将 JWT 身份验证添加到此文件下载功能?或者有其他方法吗?这些文件在服务器的文件系统中,上面的方法对内存非常友好,所以我更喜欢远离 blob。
注意:我使用的是应用程序内用户帐户。
为了确保文件下载安全,我使用下载请求 URI 中发送的一次性令牌:
- 定义一个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; }
}
我更喜欢将令牌存储在数据库中,但您可以选择将其存储在内存中,但显然是服务器端。
- 下载前创建一个令牌
这里我使用调用 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;
}
}
- 当用户点击下载时创建下载 uri link
这里我使用了一个按钮,但它适用于任何 html 元素,您可以使用 link 代替。
@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);
}
}
- 从 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);
};
});
- 定义一个服务来读取和使用来自 URI 的一次性令牌
令牌不能重复使用,因此会在每次请求时删除。
这里只是一个示例。如果将令牌存储在数据库中,则可以使用 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;
}
}
我设法在这里找到了一个解决方案,展示了如何使用 JS 注入创建控制器和下载文件:
但是,将 [Authorize] 属性添加到控制器会阻止任何尝试(即使已登录)下载文件。我希望获得授权的人只能访问下载文件。
网站的其余部分正在使用 JWT,没有任何问题。
我的问题是如何将 JWT 身份验证添加到此文件下载功能?或者有其他方法吗?这些文件在服务器的文件系统中,上面的方法对内存非常友好,所以我更喜欢远离 blob。
注意:我使用的是应用程序内用户帐户。
为了确保文件下载安全,我使用下载请求 URI 中发送的一次性令牌:
- 定义一个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; }
}
我更喜欢将令牌存储在数据库中,但您可以选择将其存储在内存中,但显然是服务器端。
- 下载前创建一个令牌
这里我使用调用 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;
}
}
- 当用户点击下载时创建下载 uri link
这里我使用了一个按钮,但它适用于任何 html 元素,您可以使用 link 代替。
@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);
}
}
- 从 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);
};
});
- 定义一个服务来读取和使用来自 URI 的一次性令牌
令牌不能重复使用,因此会在每次请求时删除。
这里只是一个示例。如果将令牌存储在数据库中,则可以使用 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;
}
}