扩展 IdentityServer4 服务

Scaling an IdentityServer4 service

我已遵循 IdentityServer4 quickstarts 并且能够使用隐式授权使用 IdentityServer 的本地托管实例验证我的 javascript 网页(几乎与快速入门中提供的相同)。同样,我的 IdentityServer 与上面提到的快速入门中提供的几乎完全相同 - 它只是有一些自定义用户详细信息。

然后我将我的应用程序 (C# .NET Core) 移动到 docker 容器中,并在 Kubernetes 集群(单个实例)中托管了一个实例,并创建了一个 Kubernetes 服务(一个或多个外观) 'real' 服务),它让我可以从集群外部访问身份服务器。我可以修改我的 JavaScript 网页并将其指向我的 Kubernetes 服务,它仍然会很高兴地显示登录页面并且它似乎按预期工作。

然后当我将 IdentityServer 扩展到三个实例(所有实例都在单个 Kubernetes 服务后提供服务)时,我开始 运行 遇到问题。 Kubernetes 服务循环请求每个身份服务器,因此第一个将显示登录页面,但第二个将在我按下登录按钮后尝试处理身份验证。这会导致以下错误:

System.InvalidOperationException: The antiforgery token could not be decrypted. ---> System.Security.Cryptography.CryptographicException: The key {19742e88-9dc6-44a0-9e89-e7b09db83329} was not found in the key ring. at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.UnprotectCore(Byte[] protectedData, Boolean allowOperationsOnRevokedKeys, UnprotectStatus& status) at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.DangerousUnprotect(Byte[] protectedData, Boolean ignoreRevocationErrors, Boolean& requiresMigration, Boolean& wasRevoked) at Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingBasedDataProtector.Unprotect(Byte[] protectedData) at Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgeryTokenSerializer.Deserialize(String serializedToken) --- End of inner exception stack trace --- ... And lots more......

所以 - 我知道我收到此错误是因为期望同一个 IdentityServer 应该为它显示的页面请求提供服务(否则防伪令牌将如何工作,对吧?),但是我想了解的是如何在复制环境中进行这项工作。

我不想在不同的 IP 上托管多个身份服务器's/ports;我正在尝试构建一个 HA 配置,如果一个 IdentityServer 挂掉,调用端点的任何事情都不应该关心(因为请求应该由其他工作实例提供服务)。

我说过我使用的是快速启动代码——这意味着在 IdentityServer 的启动中,有看起来像这样的代码...

    public void ConfigureServices(IServiceCollection services)  
    {
        services.AddMvc();

        services.AddIdentityServer(options =>
            {
                options.Events.RaiseSuccessEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseErrorEvents = true;
            })
            .AddTemporarySigningCredential()
            .AddInMemoryIdentityResources(Config.GetIdentityResources())
            .AddInMemoryApiResources(Config.GetApiResources())
            .AddInMemoryClients(Config.GetClients())

我假设我需要用我的 Kubernetes 集群中 运行 的所有 IdentityServer 实例都可以使用的证书替换 .AddTemporarySigningCredential() 逻辑。不知道 MVC 是如何工作的(MVC6 用于在 IdentityServer 服务中生成登录页面,这是我从上面的示例代码 - link 中获得的) - 我想知道是否只是更改代码以使用正确的证书在所有服务之间共享哪个足以让原型 HA IdentityServer 集群正常工作?

通过工作,我的意思是我的期望是我可以在 Kubernetes 集群中有 n 个 IdentityServer 实例 运行,有一个 Kubernetes 服务作为我拥有的许多 IdentityServer 的外观 运行,并且能够使用多个 IdentityServer 实例进行身份验证,这些实例可以共享数据,以至于它们都为我的调用 Web 应用程序提供完全相同的权限,并且可以在一个或多个实例死亡的情况下处理彼此的请求。

如有任何帮助或见解,我们将不胜感激。

我想我已经解决了这个问题。为了解决我的问题,我做了两件事:

  1. 创建我自己的 X509 证书并在我的每个 IdentityServer 之间共享该证书。网上有很多关于如何创建有效证书的示例;我刚用过

    services.AddIdentityServer(...).AddSigningCredential(new X509Certificate2(bytes, "password")
    

    在我的初创公司中 class。

  2. 深入研究 MVC 框架代码,发现我需要实现一个 Key storage provider 以便在提供登录服务的 Identity Server 的 MVC 部分的不同实例之间共享状态页。

事实证明有一个 Redis backed KSP available from NuGet,这意味着我只需要在我的 Kube 集群中启动一个私有的 redis 实例(在我的集群之外无法访问)来共享解密秘密.

/* Note: Use an IP, or resolve from DNS prior to adding redis based key store as direct DNS resolution doesn't work for this inside a K8s cluster, though it works quite happily in a Windows environment. */  
var redis = ConnectionMultiplexer.Connect("1.2.3.4:6379");
services.AddDataProtection()
        .PersistKeysToRedis(redis, "DataProtection-Keys");

我现在可以将我的身份服务扩展到 3 个实例,并让 Kube 服务充当所有可用实例的外观。我可以将日志视为身份服务之间 Kubernetes 循环请求,我的身份验证正如我预期的那样发生。

感谢在此之前对问题发表评论的人 post。

对于那些使用 Kubernetes 的人,可以使用文件系统密钥存储提供程序

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToFileSystem(new DirectoryInfo(@"/app/key-storage"));
}

其中目录“/app/key-storage”映射到 nfs 支持的持久卷。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-key-storage
spec:
  selector:
    matchLabels:
      type: nfs-pv
  storageClassName: manual
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 10Mi
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
  labels:
    type: nfs-pv
spec:
  storageClassName: manual
  capacity:
    storage: 10Mi
  accessModes:
    - ReadWriteMany
  nfs:
    server: <server>
    path: /<path>
  persistentVolumeReclaimPolicy: Delete

并且在 IDP 部署中

template:
  spec:
    containers:
      - name: <name>
        volumeMounts:
          - name: key-storage
            mountPath: /app/key-storage
            readOnly: false
    volumes:
      - name: key-storage
        persistentVolumeClaim:
        claimName: pvc-key-storage

并且您需要签名证书。这可以作为秘密添加,然后 IDP 部署可以使用另一个卷来装载秘密(未显示)。

apiVersion: v1
kind: Secret
metadata:
  name: cert-secret
  labels:
    app: <app-label>
type: Opaque
data:
  signingcert.pfx: <base64 cert value>