【问题标题】:Running multiple instances of Identity server in docker cluster在 docker 集群中运行多个身份服务器实例
【发布时间】:2020-03-20 11:25:18
【问题描述】:

我有一个 .net core 2.0 应用程序,基本上是身份服务器 4。当我只有一个实例时,它运行得很好。但是,如果我尝试运行多个身份服务器实例,我就会开始遇到问题。

第一期

处理请求时发生未处理的异常。 CryptographicException:在密钥环中找不到密钥 {ec55dd66-7caf-4423-9dd6-74768e80675d}。 Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.UnprotectCore(Byte[] protectedData, bool allowOperationsOnRevokedKeys, out UnprotectStatus status)

InvalidOperationException:无法解密防伪令牌。 Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryTokenSerializer.Deserialize(string serializedToken)

我能够确定这是因为密钥是在身份服务器的所有实例上生成的,而不是只生成一个密钥并且他们都使用了它。

我添加了以下代码。

services.AddDataProtection()
            .PersistKeysToFileSystem(new DirectoryInfo(settingsSetup.Settings.PersistKeysDirectory))
            .SetDefaultKeyLifetime(TimeSpan.FromDays(90))
            .SetApplicationName($"Awesome-IdentityServer-{_env.EnvironmentName}"); 

这基本上告诉身份服务器将密钥存储在哪里。我按照这里找到的说明Persisting keys when hosting in a Docker container 所以我有A folder that's a Docker volume that persists beyond the container's lifetime, such as a shared volume or a host-mounted volume.

不幸的是,这没有奏效,我现在收到以下错误

“idsrv 未通过身份验证。失败消息:取消保护票证失败”

我认为这意味着密钥需要以某种方式加密。

什么是Unprotect ticket failed,我该如何解决?我可以在 docker 节点中运行多个身份服务器实例吗?

现在有了加密功能。

services.AddDataProtection()
            .PersistKeysToFileSystem(new DirectoryInfo(settingsSetup.Settings.PersistKeysDirectory))
            .SetDefaultKeyLifetime(TimeSpan.FromDays(90))
            .ProtectKeysWithCertificate(LoadCertificate())
            .SetApplicationName($"Awesome-IdentityServer-{_env.EnvironmentName}");

身份服务器在日志中响应以下错误。

未配置 XML 加密器。密钥 {2e0f629c-9dca-44fa-922e-5c5613bd27c8} 可能会以未加密的形式保存到存储中。

向用户显示此错误

CryptographicException:无法检索解密密钥。 System.Security.Cryptography.Xml.EncryptedXml.GetDecryptionKey(EncryptedData encryptedData, string symmetricAlgorithmUri)

Authentication with Docker in ASP.NET Core 还提到这应该可以工作。

我最初认为这是 Identity Server 4 的问题,因为他们的文档表明它是无状态的。在与他们来来回回之后,我在 GitHub Stateless or not Stateless 上发布了一个问题,我倾向于认为这更像是一个 docker 问题,而不是身份服务器问题。

【问题讨论】:

  • 感觉就像你必须跳过很多圈才能实现这一点。我们使用多台服务器运行负载平衡设置,并使用共享数据库通过实现 IXmlRepository 接口来存储密钥,它工作正常。如果您也想横向扩展,共享数据库更有意义。
  • 这将是我的下一步。它只有一个密钥文件,所以对我来说,把它放在数据库接缝中的目录中是有意义的,但我可能别无选择。 (我没有提到它,但我们也设置了负载平衡,幸运的是,这个马戏团的一部分不是我的工作)
  • 这看起来像是正确的文档,可以继续使用:)
  • 顺便说一句,密钥不必加密即可工作。你可以先不加密调试:)

标签: docker .net-core identityserver4


【解决方案1】:

我在客户中看到的常见问题是他们将AddDataProtection 注册放在AddIdentityServer 之前。

AddIdentityServer 还使用默认设置调用AddDataProtection,不幸的是,这将覆盖您之前所做的任何注册。

如此简短的回答:您需要在您的ConfigureServices 方法中将AddDataProtection 放在AddIdentityServer 之后。

【讨论】:

  • 我明天试试,然后回复你。感谢您的提示
【解决方案2】:

我为此苦苦挣扎了好几天,试图实现一个共享的 docker 卷,但即使使用证书加密密钥也无法正常工作。所以我最终使用了数据库 - 正如 @mackie 所建议的那样。

以下是一些灵感代码:

 X509Certificate2  cert = X509CertificateHelper.GetCertificateFromFile(options.Certificate.FilePath, options.Certificate.Passphrase);
 builder.AddSingleton<IXmlRepository, MongoXmlRepository>();
 var sp = builder.BuildServiceProvider();
 builder.AddSingleton<IXmlRepository, MongoXmlRepository>();
 builder.AddDataProtection()
        .SetApplicationName(Assembly.GetExecutingAssembly().FullName)
        .AddKeyManagementOptions(o =>
                {
                    o.XmlRepository = sp.GetService<IXmlRepository>();
                })
        .ProtectKeysWithCertificate(cert)
        .SetDefaultKeyLifetime(TimeSpan.FromDays(options.LifeTimeInDays));

MongoXmlRepository 只是一个实现 IXmlRepository 接口的类:

/// <summary>
/// Implementation of the IXmlRepository, used by the DataProtection Service
/// </summary>
public class MongoXmlRepository : CrudRepository<MongoXElement, string>, IXmlRepository
{
    public MongoXmlRepository(ILoggerFactory loggerFactory,
        DatabaseRegistry databaseRegistry)
        : base(loggerFactory, databaseRegistry)
    {

    }
    public IReadOnlyCollection<XElement> GetAllElements()
    {
        try
        {
            var asyncCursor = this.Collection
                .Find(Builders<MongoXElement>.Filter.Empty);
            var entities = asyncCursor.ToList();
            var xml = entities.Select(e => XElement.Parse(e.Xml)).ToList();
            return xml;
        }
        catch (Exception e)
        {
            var errMessage = $"MongoXmlRepositoryError on {this.GetType()}:GetAllElements()";
            this.Logger.LogError(errMessage, e);
            throw new RepositoryException(errMessage, e);
        }
    }

    public async void StoreElement(XElement element, string friendlyName)
    {
        var key = new MongoXElement
        {
            Xml = element.ToString(SaveOptions.DisableFormatting)
        };
        await this.InsertAsync(key);


    }
}

实现可能因您的商店而异,在本例中使用的是 MongoDb。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-02
    • 1970-01-01
    • 2015-12-18
    • 2016-03-03
    • 1970-01-01
    • 1970-01-01
    • 2012-09-15
    相关资源
    最近更新 更多