扩展IdentityServer4服务

21

我已经按照IdentityServer4快速入门并且可以使用隐式授权,通过一个本地实例的 IdentityServer 验证我的 JavaScript 网页(与快速入门中提供的几乎相同)。我的 IdentityServer 几乎与上述快速入门中提供的完全相同-它只有一些自定义用户详细信息。

然后我将我的 C# .NET Core 应用程序移动到 Docker 容器中,在 Kubernetes 集群(单个实例)中托管了一个实例,并创建了一个 Kubernetes 服务(一个或多个“真实”服务的外观),让我可以从集群外部访问身份验证服务器。我可以修改我的 JavaScript 网页并将其指向我的 Kubernetes 服务,它仍然可以愉快地显示登录页面并且似乎符合预期。

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

System.InvalidOperationException: 无法解密防伪标记。---> System.Security.Cryptography.CryptographicException: 在密钥环中未找到密钥 {19742e88-9dc6-44a0-9e89-e7b09db83329}。 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 --- ...更多错误信息...

所以我理解我得到这个错误是因为期望同一个 IdentityServer 应该服务于它显示的页面的请求(否则防伪标记如何工作,对吧?),但我试图理解的是如何在复制的环境中使其工作。

我不想在不同的 IP/端口上托管多个身份验证服务器。我试图构建一个高可用配置,如果一个 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())

我假设我需要用一个证书替换 .AddTemporarySigningCredential()的逻辑,这个证书可以被运行在Kubernetes集群中的所有IdentityServer实例使用。由于不知道MVC是如何工作的(MVC6用于生成IdentityServer服务中的登录页面,我从上面的示例代码中获取了它),我想知道是否只需更改代码以使用适当的证书即可使原型HA IdentityServer集群正常工作?

所谓的工作是指,我期望在Kubernetes集群中运行n个IdentityServer实例,有一个Kubernetes服务作为所有IdentityServer的门面,并能够使用多个IdentityServer实例进行身份验证,它们可以共享数据,以便为我的调用Web应用程序提供完全相同的授权,并且可以在一个或多个实例出现故障时处理彼此的请求。

任何帮助或见解都将不胜感激。


2
添加TemporarySingingCrendentials将为每个pod生成不同的值。您肯定需要一个静态证书。请参见.AddSigningCredential。但我不确定这是否是唯一的问题,因为我计划做一些非常类似且相当有趣的事情。 - Boas Enkler
1
我们在负载均衡环境中使用身份验证服务器没有问题,但我们没有使用由IdSvr生成的登录页面流程。我们使用单个签名证书(而不是临时证书),这很容易设置/测试,因此您应该能够快速确定您的问题是临时证书还是其他问题。 - Mashton
@Mashton,了解这点很好;我之前遇到了一些问题,所以还没有尝试Boas建议的.AddSigningCredential()。等我更新证书后会进行更新,可能要等到周末之后了。 - Jay
为什么这篇帖子被踩了?如果能留下一条说明他们认为哪里有问题的评论就好了。 - Jay
3个回答

18

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

  1. Create my own X509 certificate and shared this certificate between each of my IdentityServer's. There are lots of examples of how to create valid certificates on the net; I just used

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

    in my startup class.

  2. Dug into the MVC framework code and worked out that I needed to implement a Key storage provider in order to share state between different instances of the MVC part of Identity Server which serves up the login page.

原来有一个由NuGet提供的支持Redis的KSP,这意味着我只需要在我的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在身份验证服务之间轮询请求,我的身份验证正如我所期望的那样进行。
感谢在此帖子之前评论问题的人。

我也做了这两件事,但我仍然有这个问题。 - heavenwing
嘿@jay,快问一个问题。如果存储密钥的Redis服务器被擦除了会发生什么?您是否需要重新登录所有身份服务器实例,还是应用程序会自动生成新的密钥,可以与旧cookie一起使用?谢谢! - bobek
@HubertJarema - 我不能说我注意到需要做任何除了重新登录的事情。自从这篇文章以来,我已经清除了我的 Redis 数据库数百次,但我从未因此而感到有任何问题值得关注 :) - Jay

3

对于使用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>

1
嘿,我正在尝试实现这个(在Kubernetes Pod之间使用共享文件夹),但仍然在数据保护方面遇到问题。是否还涉及其他配置?谢谢。 - Vithozor

0
1. 使配置和操作数据存储持久化。 2. 使用X509证书对密钥进行签名和验证。 3. 添加数据保护并保持密钥共享(在Redis、文件存储或Blob存储中)。
如果需要这些代码块,请告诉我。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接