asp.net核心JWT在uri查询参数中?

时间:2017-08-18 18:42:08

标签: asp.net-core asp.net-core-mvc jwt url-parameters

我有一个受JWT和Authorize属性保护的api,在客户端我使用jquery ajax调用来处理它。

这很好用,但我现在需要能够保证文件的下载安全,这样我才能设置标头承载值,可以在URI中作为url参数完成吗?

= - = - = - = -

更新:这是我最终为我的场景做的事情,这是一个内部项目,而且数量非常少但安全性很重要,未来可能需要扩展:

当用户登录时,我生成一个随机下载密钥并将其放入数据库中的用户记录以及其JWT的到期日期,并将下载密钥返回给客户端。如果存在具有下载密钥且该密钥存在于用户记录中并且未到期的查询参数,则下载路由受到保护以仅允许下载。这样,dl密钥对于每个用户是唯一的,只要用户的auth会话有效并且可以容易地撤销,就是有效的。

5 个答案:

答案 0 :(得分:6)

这是一个常见问题。

每当您想直接从单个页面应用程序的HTML中的API引用图像或其他文件时,都无法在Authorization或{{1之间插入<img>请求标头}}元素和对API的请求。您可以使用here中所述的一些相当新的浏览器功能来回避此问题,但是您可能需要支持缺少此功能的浏览器。

幸运的是,RFC 6750指定了一种通过the "URI Query Parameter" authentication approach准确执行您所要求的方法的方法。如果遵循其约定,您将使用以下格式接受JWT:

  

https://server.example.com/resource?access_token=mF_9.B5f-4.1JqM&p=q

如另一个answer和RFC 6750本身所述,您应该仅在必要时这样做。从RFC:

  

由于与URI方法相关的安全性弱点(请参见Section 5),包括包含访问令牌的URL被记录的可能性很高,因此除非无法传输访问权限,否则不应该使用它“授权”请求标头字段或HTTP请求实体正文中的令牌。

如果您仍然决定实施“ URI查询参数”身份验证,则可以使用Invio.Extensions.Authentication.JwtBearer库并在AddQueryStringAuthentication()上调用JwtBearerOptions扩展方法。或者,如果您想手动执行此操作,当然也可以执行此操作。这是一个代码示例,将两种方式都显示为Microsoft.AspNetCore.Authentication.JwtBearer库的扩展。

<a>

答案 1 :(得分:4)

虽然在技术上可以在网址中加入JWT,但强烈不鼓励。请参阅here中的引文,该引文解释了为什么这是一个坏主意:

  

不要在页面网址中传递持票人令牌:持票人令牌不应该是   传入页面URL(例如,作为查询字符串参数)。   相反,承载令牌应该在HTTP消息头或传递中传递   采取保密措施的信息机构。浏览器,   Web服务器和其他软件可能无法充分保护网站中的URL   浏览器历史记录,Web服务器日志和其他数据结构。如果是持票人   令牌在页面URL中传递,攻击者可能会窃取它们   来自历史数据,日志或其他不安全的位置。

答案 2 :(得分:1)

如果您仍然需要它,则必须在localStorage上设置jwt标记。之后,您必须使用以下代码创建新标头:

'functionName'():Headers{
        let header =new Headers();
        let token = localStorage.getItem('token')
        header.append('Authorization',`Bearer ${token}`);

        return header;
    }

将Hader添加到http请求。

return this.http.get('url',new RequestOptions({headers:this.'serviceName'.'functionName'()}))

答案 3 :(得分:1)

您可以使用中间件从查询参数中设置授权标头:

        public class SecureDownloadUrlsMiddleware
        {
            private readonly RequestDelegate next;

            public SecureDownloadUrlsMiddleware(RequestDelegate next)
            {
                this.next = next;
            }

            public async Task Invoke(HttpContext context /* other dependencies */)
            {
                // get the token from query param
                var token = context.Request.Query["t"];
                // set the authorization header only if it is empty
                if (string.IsNullOrEmpty(context.Request.Headers["Authorization"]) &&
                    !string.IsNullOrEmpty(token))
                {
                    context.Request.Headers["Authorization"] = $"Bearer {token}";
                }
                await next(context);
            }
        }

,然后在Startup.cs中使用中间件,然后使用身份验证中间件:

app.UseMiddleware(typeof(SecureDownloadUrlsMiddleware));
app.UseAuthentication();

答案 4 :(得分:-1)

尽管这是一些开箱即用的方法,但我还是建议您这样做,因为这是在.NET环境中进行开发时最好的可伸缩解决方案。

使用Azure存储!或任何其他类似的在线云存储解决方案。

  1. 这可确保您的Web应用程序与文件分开,因此您不必担心将应用程序移动到其他Web环境。
  2. 与天青存储(1GB约需进行3000次操作(读/写/列表)的总成本约为0.03美元)相比,网络存储的价格要昂贵得多。
  3. 在停机时间更为紧急的情况下扩展应用程序时,使用交换/分段技术也适用于第1点。
  4. Azure存储负责所谓的Shared Access Tokens (SAS)
  5. 的到期

为了您的简便起见,我将在此处包括我的代码,这样您就不必搜索其余的

因此,我的情况是,我所有文件都保存为数据库中的Attachments(当然不是实际文件)。

当有人请求附件时,我会进行快速检查以查看过期日期是否已过,如果是,我们应该生成一个新的网址。

//where ever you want this to happen, in the controller before going to the client for example
private async Task CheckSasExpire(IEnumerable<AttachmentModel> attachments)
{
    foreach (AttachmentModel attachment in attachments)
    {
        await CheckSasExpire(attachment);
    }
}
private async Task CheckSasExpire(AttachmentModel attachment)
{
    if (attachment != null && attachment.LinkExpireDate < DateTimeOffset.UtcNow && !string.IsNullOrWhiteSpace(attachment.AzureContainer))
    {
        Enum.TryParse(attachment.AzureContainer, out AzureStorage.ContainerEnum container);
        string url = await _azureStorage.GetFileSasLocator(attachment.Filename, container);
        attachment.FileUrl = url;
        attachment.LinkExpireDate = DateTimeOffset.UtcNow.AddHours(1);
        await _attachmentRepository.UpdateAsync(attachment.AttachmentId, attachment);
    }
}

AzureStorage.ContainerEnum只是一个内部枚举,可以轻松地跟踪某些文件存储在其中的容器,但是这些当然可以是字符串

还有我的AzureStorage类:

using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
public async Task<string> GetFileSasLocator(string filename, ContainerEnum container, DateTimeOffset expire = default(DateTimeOffset))
{
    var cont = await GetContainer(container);
    CloudBlockBlob blockBlob = cont.GetBlockBlobReference(filename);
    DateTimeOffset expireDate = DateTimeOffset.UtcNow.AddHours(1);//default
    if (expire != default(DateTimeOffset) && expire > expireDate)
    {
        expireDate = expire.ToUniversalTime();
    }

    SharedAccessBlobPermissions permission = SharedAccessBlobPermissions.Read;
    var sasConstraints = new SharedAccessBlobPolicy
    {
        SharedAccessStartTime = DateTime.UtcNow.AddMinutes(-30),
        SharedAccessExpiryTime = expireDate,
        Permissions = permission
    };
    var sasToken = blockBlob.GetSharedAccessSignature(sasConstraints);
    return blockBlob.Uri + sasToken;
}

private async Task<CloudBlobContainer> GetContainer(ContainerEnum container)
{
    //CloudConfigurationManager.GetSetting("StorageConnectionString")
    CloudStorageAccount storageAccount = CloudStorageAccount.Parse(_config["StorageConnectionString"]);
    CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
    string containerName = container.ToString().ToLower();
    CloudBlobContainer cloudContainer = blobClient.GetContainerReference(containerName);
    await cloudContainer.CreateIfNotExistsAsync();
    return cloudContainer;
}

因此这将产生如下网址:http://127.0.0.1:10000/devstoreaccount1/invoices/NL3_2002%20-%202019-04-12.pdf?sv=2018-03-28&sr=b&sig=gSiohA%2BGwHj09S45j2Deh%2B1UYP1RW1Fx5VGeseNZmek%3D&st=2019-04-18T14%3A16%3A55Z&se=2019-04-18T15%3A46%3A55Z&sp=r

当然,如果允许用户查看文件,则在检索附件时必须应用自己的身份验证逻辑。但这一切都可以通过JWT令牌以及在控制器或存储库中完成。我不会担心URL是公共URL,如果一个人非常有可能在一小时之内获得该URL,那么请减少过期日期:D