【问题标题】:ASP.NET CORE 2.0 Distributed Session Management with AWS DynamoDBASP.NET CORE 2.0 分布式会话管理与 AWS DynamoDB
【发布时间】:2019-03-11 02:03:05
【问题描述】:

我正在寻找使用 Amazon Dynamodb 在 ASP.NET CORE 2.0 中实现分布式会话管理。但找不到任何文档或示例源代码。

如何使用 ASP.NET CORE 2.0 和 DynamoDb 实现分布式会话管理?

【问题讨论】:

标签: asp.net-core amazon-dynamodb asp.net-core-2.0


【解决方案1】:

我已经为 ASP.NET Core 分布式会话状态实现了 AWS DynamoDB,包括在 DynamoDB 中存储会话 cookie 加密密钥(您必须将密钥存储在某处,以便您的应用程序的不同实例可以相互解码 cookie) .

请注意,这是一个“简单的”实现,我还没有使它可测试,或者使用 DI 等。这两个类是 DynamoDbCache 和 DdbXmlRepository。

DynamoDBCache.cs:

using System;
using System.Threading;
using System.Threading.Tasks;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;

namespace Microsoft.Extensions.Caching.DynamoDb
{
    public class DynamoDbCache : IDistributedCache
    {
        private static IAmazonDynamoDB _client;
        private static Table _table;

        private string _tableName = "ASP.NET_SessionState";
        private string _ttlfield = "TTL";
        private int _sessionMinutes = 20;
        private enum ExpiryType
        {
            Sliding,
            Absolute
        }

        public DynamoDbCache(IOptions<DynamoDbCacheOptions> optionsAccessor, IAmazonDynamoDB dynamoDb)
        {
            _client = dynamoDb;

            if (optionsAccessor != null)
            {
                _tableName = optionsAccessor.Value.TableName;
                _ttlfield = optionsAccessor.Value.TtlAttribute;
                _sessionMinutes = (int)optionsAccessor.Value.IdleTimeout.TotalMinutes;
            }

            if (_client == null)
            {
                _client = new AmazonDynamoDBClient();
            }

            if (_table == null)
            {
                _table = Table.LoadTable(_client, _tableName);
            }
        }

        public byte[] Get(string key)
        {
            return GetAsync(key).Result;
        }

        public async Task<byte[]> GetAsync(string key, CancellationToken token = default(CancellationToken))
        {
            var value = await _table.GetItemAsync(key);
            if (value == null || value["Session"] == null)
            {
                return null;
            }

            return value["Session"].AsByteArray();
        }

        public void Refresh(string key)
        {
            var value = _table.GetItemAsync(key).Result;
            if (value == null || value["ExpiryType"] == null || value["ExpiryType"] != "Sliding")
            {
                return;
            }
            value[_ttlfield] = DateTimeOffset.Now.ToUniversalTime().ToUnixTimeSeconds() + (_sessionMinutes * 60);

            Task.Run(() => Set(key, value["Session"].AsByteArray(), new DistributedCacheEntryOptions { SlidingExpiration = new TimeSpan(0, _sessionMinutes, 0) }));
        }

        public async Task RefreshAsync(string key, CancellationToken token = default(CancellationToken))
        {
            var value = _table.GetItemAsync(key).Result;
            if (value == null || value["ExpiryType"] == null || value["ExpiryType"] != "Sliding")
            {
                return;
            }
            value[_ttlfield] = DateTimeOffset.Now.ToUniversalTime().ToUnixTimeSeconds() + (_sessionMinutes * 60);

            await SetAsync(key, value["Session"].AsByteArray(), new DistributedCacheEntryOptions { SlidingExpiration = new TimeSpan(0, _sessionMinutes, 0) });
        }

        public void Remove(string key)
        {
            _table.DeleteItemAsync(key).Wait();
        }

        public async Task RemoveAsync(string key, CancellationToken token = default(CancellationToken))
        {
            await _table.DeleteItemAsync(key);
        }

        public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
        {
            SetAsync(key, value, options).Wait();
        }

        public async Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken))
        {
            ExpiryType expiryType;
            var epoctime = GetEpochExpiry(options, out expiryType);
            var _ssdoc = new Document();

            _ssdoc.Add("SessionId", key);
            _ssdoc.Add("Session", value);
            _ssdoc.Add("CreateDate", DateTime.Now.ToUniversalTime().ToString("o"));
            _ssdoc.Add("ExpiryType", expiryType.ToString());
            _ssdoc.Add(_ttlfield, epoctime);

            await _table.PutItemAsync(_ssdoc);
        }

        private long GetEpochExpiry(DistributedCacheEntryOptions options, out ExpiryType expiryType)
        {
            if (options.SlidingExpiration.HasValue)
            {
                expiryType = ExpiryType.Sliding;
                return DateTimeOffset.Now.ToUniversalTime().ToUnixTimeSeconds() + (long)options.SlidingExpiration.Value.TotalSeconds;

            }
            else if (options.AbsoluteExpiration.HasValue)
            {
                expiryType = ExpiryType.Absolute;
                return options.AbsoluteExpiration.Value.ToUnixTimeSeconds();

            }
            else if (options.AbsoluteExpirationRelativeToNow.HasValue)
            {
                expiryType = ExpiryType.Absolute;
                return DateTimeOffset.Now.Add(options.AbsoluteExpirationRelativeToNow.Value).ToUniversalTime().ToUnixTimeSeconds();
            }
            else
            {
                throw new Exception("Cache expiry option must be set to Sliding, Absolute or Absolute relative to now");
            }
        }
    }
}

和 DdbXmlRepository:

using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DataModel;
using Microsoft.AspNetCore.DataProtection.Repositories;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

namespace Website.Session
{
    public class DdbXmlRepository : IXmlRepository
    {
        private static IAmazonDynamoDB _dynamoDb;

        public DdbXmlRepository(IAmazonDynamoDB dynamoDb)
        {
            _dynamoDb = dynamoDb;
        }

        public IReadOnlyCollection<XElement> GetAllElements()
        {

            var context = new DynamoDBContext(_dynamoDb);
            var search = context.ScanAsync<XmlKey>(new List<ScanCondition>());
            var results = search.GetRemainingAsync().Result;

            return results.Select(x => XElement.Parse(x.Xml)).ToList();
        }

        public void StoreElement(XElement element, string friendlyName)
        {
            var key = new XmlKey
            {
                Xml = element.ToString(SaveOptions.DisableFormatting),
                FriendlyName = friendlyName
            };

            var context = new DynamoDBContext(_dynamoDb);
            context.SaveAsync(key).Wait();
        }
    }

    [DynamoDBTable("AspXmlKeys")]
    public class XmlKey
    {
        [DynamoDBHashKey]
        public string KeyId { get; set; } = Guid.NewGuid().ToString();
        public string Xml { get; set; }
        public string FriendlyName { get; set; }
    }
}

您还需要一个服务收集扩展:

using System;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.DynamoDb;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class DynamoDbCacheServiceCollectionExtensions
    {
        /// <summary>
        /// Adds Amazon DynamoDB caching services to the specified <see cref="IServiceCollection" />.
        /// </summary>
        /// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
        /// <param name="setupAction">An <see cref="Action{DynamoDbCacheOptions}"/> to configure the provided
        /// <see cref="DynamoDbCacheOptions"/>.</param>
        /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
        public static IServiceCollection AddDistributedDynamoDbCache(this IServiceCollection services, Action<DynamoDbCacheOptions> setupAction)
        {
            if (services == null)
            {
                throw new ArgumentNullException(nameof(services));
            }

            if (setupAction == null)
            {
                throw new ArgumentNullException(nameof(setupAction));
            }

            services.AddOptions();
            services.Configure(setupAction);
            services.Add(ServiceDescriptor.Singleton<IDistributedCache, DynamoDbCache>());

            return services;
        }
    }
}

最后我使用了一个选项类来设置表名、默认过期时间等:

using Microsoft.Extensions.Options;
using System;

namespace Microsoft.Extensions.Caching.DynamoDb
{
    public class DynamoDbCacheOptions : IOptions<DynamoDbCacheOptions>
    {
        public string TableName { get; set; } = "ASP.NET_SessionState";
        public TimeSpan IdleTimeout { get; set; } = new TimeSpan(0, 20, 0);
        public string TtlAttribute { get; set; } = "TTL";

        DynamoDbCacheOptions IOptions<DynamoDbCacheOptions>.Value
        {
            get { return this; }
        }
    }
}

在您的启动中,您需要在 ConfigureServices 中使用如下代码将其全部连接起来:

services.AddDefaultAWSOptions(Configuration.GetAWSOptions());
            services.AddAWSService<IAmazonDynamoDB>();
services.AddSingleton<IXmlRepository, DdbXmlRepository>();

            services.AddDistributedDynamoDbCache(o => {
                o.TableName = "TechSummitSessionState";
                o.IdleTimeout = TimeSpan.FromMinutes(30);
            });

            services.AddSession(o => {
                o.IdleTimeout = TimeSpan.FromMinutes(30);
                o.Cookie.HttpOnly = false;
            });
services.AddDataProtection()
                .AddKeyManagementOptions(o => o.XmlRepository = sp.GetService<IXmlRepository>());

希望这会有所帮助!它适用于我的应用 :-)

【讨论】:

  • 如果不清楚,还需要创建DynamoDB表“AspXmlKeys”,KeyId为分区键。
  • 效果很好,谢谢。希望 AWS 团队最终会更新他们的 AWS.SessionProvider NuGet 包以支持 .NET Core。
  • @StuartWelch - 我们实际上为 DataProtection 部分(持久 cookie 加密密钥)发布了一个 NuGet 包,它将它们存储在 Systems Manager 的 Parameter Store 中,这是一个更好的解决方案。见这里:nuget.org/packages/Amazon.AspNetCore.DataProtection.SSM 如果你使用那个包,你就不需要 DdbXmlRepository 类。
  • 谢谢@Kirkaiya,我已经切换到这个库(使用 KMS 密钥)并且运行良好。 services.AddDataProtection().PersistKeysToAWSSystemsManager("/[MyAppName]/DataProtection", options =&gt; { options.KMSKeyId = "[MyKeyId]"; });
猜你喜欢
  • 2012-12-12
  • 2013-01-06
  • 2021-03-02
  • 1970-01-01
  • 1970-01-01
  • 2016-06-04
  • 1970-01-01
  • 2013-07-22
  • 1970-01-01
相关资源
最近更新 更多