如何在 docker 机器的内部和外部使用 IdentityServer4?

How can I use IdentityServer4 from inside and outside a docker machine?

我希望能够从 docker 机器的外部和内部针对 Identity Server (STS) 进行身份验证。

我在设置适用于容器内外的正确权限时遇到问题。如果我将权限设置为内部名称 mcoidentityserver:5000,则 API 可以进行身份​​验证,但客户端无法获得令牌,因为客户端位于 docker 网络之外。如果我将权限设置为外部名称 localhost:5000,则客户端可以获得令牌,但 API 无法识别权限名称(因为 localhost 在这种情况下是主机)。

Authority 应该设置成什么?或者我可能需要调整 docker 网络?

图表

红色箭头是我遇到问题的部分。

详情

我正在设置 Windows 10 docker 开发环境,它使用 ASP.NET Core API(在 Linux 上),Identity Server 4 ( ASP.NET Linux) 的核心和一个 PostgreSQL 数据库。 PostgreSQL 不是问题,包含在图表中是为了完整性。它映射到 9876,因为我现在在主机上还有一个 PostgreSQL 实例 运行ning。 mco为本公司简称。

我一直跟着Identity Server 4 instructions起床运行宁

代码

我没有包括 docker-compose.debug.yml 因为它有 运行 命令只与 运行ning 在 Visual Studio.

相关

docker-compose.yml

version: '2'

services:
mcodatabase:
    image: mcodatabase
    build:
    context: ./Data
    dockerfile: Dockerfile
    restart: always
    ports:
    - 9876:5432
    environment:
    POSTGRES_USER: mcodevuser
    POSTGRES_PASSWORD: password
    POSTGRES_DB: mcodev
    volumes:
    - postgresdata:/var/lib/postgresql/data
    networks:
    - mconetwork

mcoidentityserver:
    image: mcoidentityserver
    build:
    context: ./Mco.IdentityServer
    dockerfile: Dockerfile
    ports:
    - 5000:5000
    networks:
    - mconetwork

mcoapi:
    image: mcoapi
    build:
    context: ./Mco.Api
    dockerfile: Dockerfile
    ports:
    - 56107:80
    links:
    - mcodatabase
    depends_on:
    - "mcodatabase"
    - "mcoidentityserver"
    networks:
    - mconetwork

volumes:
postgresdata:

networks:
mconetwork:
    driver: bridge

docker-compose.override.yml

这是由 Visual Studio 插件创建的,用于注入额外的值。

version: '2'

services:
mcoapi:
    environment:
    - ASPNETCORE_ENVIRONMENT=Development
    ports:
    - "80" 

mcoidentityserver:
    environment:
    - ASPNETCORE_ENVIRONMENT=Development
    ports:
    - "5000" 

API Dockerfile

FROM microsoft/aspnetcore:1.1
ARG source
WORKDIR /app
EXPOSE 80
COPY ${source:-obj/Docker/publish} .
ENTRYPOINT ["dotnet", "Mco.Api.dll"]

身份服务器 Dockerfile

FROM microsoft/aspnetcore:1.1
ARG source
WORKDIR /app
COPY ${source:-obj/Docker/publish} .
EXPOSE 5000
ENV ASPNETCORE_URLS http://*:5000
ENTRYPOINT ["dotnet", "Mco.IdentityServer.dll"]

API Startup.cs

我们告诉 API 使用身份服务器并设置授权。

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
    {
        // This can't work because we're running in docker and it doesn't understand what localhost:5000 is!
        Authority = "http://localhost:5000", 
        RequireHttpsMetadata = false,

        ApiName = "api1"
    });

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

身份服务器Startup.cs

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddIdentityServer()
            .AddTemporarySigningCredential()
            .AddInMemoryApiResources(Config.GetApiResources())
            .AddInMemoryClients(Config.GetClients());
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole();

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseIdentityServer();

        app.Run(async (context) =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    }
}

身份服务器Config.cs

public class Config
{
    public static IEnumerable<ApiResource> GetApiResources()
    {
        return new List<ApiResource>
        {
            new ApiResource("api1", "My API")
        };
    }

    public static IEnumerable<Client> GetClients()
    {
        return new List<Client>
        {
            new Client
            {
                ClientId = "client",

                // no interactive user, use the clientid/secret for authentication
                AllowedGrantTypes = GrantTypes.ClientCredentials,

                // secret for authentication
                ClientSecrets =
                {
                    new Secret("secret".Sha256())
                },

                // scopes that client has access to
                AllowedScopes = { "api1" }
            }
        };
    }
}

客户端

运行 在控制台应用程序中。

var discovery = DiscoveryClient.GetAsync("localhost:5000").Result;
var tokenClient = new TokenClient(discovery.TokenEndpoint, "client", "secret");
var tokenResponse = tokenClient.RequestClientCredentialsAsync("api1").Result;

if (tokenResponse.IsError)
{
    Console.WriteLine(tokenResponse.Error);
    return 1;
}

var client = new HttpClient();
client.SetBearerToken(tokenResponse.AccessToken);

var response = client.GetAsync("http://localhost:56107/test").Result;
if (!response.IsSuccessStatusCode)
{
    Console.WriteLine(response.StatusCode);
}
else
{
    var content = response.Content.ReadAsStringAsync().Result;
    Console.WriteLine(JArray.Parse(content));
}

提前致谢。

确保 IssuerUri 设置为显式常量。我们在通过 IP/hostname 访问 Identity Server 实例时遇到了类似的问题,并以这种方式解决了它:

services.AddIdentityServer(x =>
{
    x.IssuerUri = "my_auth";
})

P.S。为什么不把权限URL统一到hostname:5000?是的,ClientAPI 都可以调用相同的 URL hostname:5000 如果:

  • 5000端口暴露了(我看还行)
  • DNS 在 docker 容器内解析。
  • 您可以访问 hostname:5000(检查防火墙、网络拓扑等)

DNS 是最棘手的部分。如果您遇到任何问题,我建议您尝试通过其暴露的 IP 访问 Identity Server,而不是解析 hostname.

为了完成这项工作,我需要在 docker-compose.yml 和设置 CORS on the identity server instance so that the API was allowed to call it. Setting up CORS is outside the remit of this question; 中传递两个环境变量。

Docker-撰写更改

身份服务器需要IDENTITY_ISSUER,这是身份服务器会给自己的名称。在本例中,我使用了 docker 主机的 IP 和身份服务器的端口。

  mcoidentityserver:
    image: mcoidentityserver
    build:
      context: ./Mco.IdentityServer
      dockerfile: Dockerfile
    environment:
      IDENTITY_ISSUER: "http://10.0.75.1:5000"
    ports:
       - 5000:5000
    networks:
     - mconetwork

API 需要知道权限在哪里。我们可以使用 docker 网络名称作为权限,因为调用不需要到 docker 网络之外, API 只是调用身份服务器来检查令牌。

  mcoapi:
    image: mcoapi
    build:
      context: ./Mco.Api
      dockerfile: Dockerfile
    environment:
      IDENTITY_AUTHORITY: "http://mcoidentityserver:5000"
    ports:
       - 56107:80
    links:
     - mcodatabase
     - mcoidentityserver
    depends_on:
     - "mcodatabase"
     - "mcoidentityserver"
    networks:
     - mconetwork

在 C# 中使用这些值

身份Server.cs

您在 ConfigureServices 中设置身份颁发者名称:

    public void ConfigureServices(IServiceCollection services)
    {
        var sqlConnectionString = Configuration.GetConnectionString("DefaultConnection");

        services
            .AddSingleton(Configuration)
            .AddMcoCore(sqlConnectionString)
            .AddIdentityServer(x => x.IssuerUri = Configuration["IDENTITY_ISSUER"])
            .AddTemporarySigningCredential()
            .AddInMemoryApiResources(Config.GetApiResources())
            .AddInMemoryClients(Config.GetClients())
            .AddCorsPolicyService<InMemoryCorsPolicyService>()
            .AddAspNetIdentity<User>();
    }

API Startup.cs

我们现在可以将权限设置为环境变量。

app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
    {
        Authority = Configuration["IDENTITY_AUTHORITY"],
        RequireHttpsMetadata = false,
        ApiName = "api1"
    });

缺点

如此处所示,docker-compose 不适合生产,因为硬编码身份颁发者是本地 IP。相反,您需要一个正确的 DNS 条目,该条目将映射到其中包含身份服务器 运行 的 docker 实例。为此,我将创建一个 docker-compose 覆盖文件并使用覆盖值构建产品。

感谢 ilya-chumakov 的帮助。

编辑

除此之外,我在我的博客上写了构建 Linux docker + ASP.NET Core 2 + OAuth with Identity Server 的整个过程。

如果您是 运行 您的 docker 个容器在同一网络中,您可以执行以下操作:

  1. 在您的身份服务器中添加 IssuerUri
services.AddIdentityServer(x =>
            {
                x.IssuerUri = "http://<your_identity_container_name>";
            })

这将设置您的身份服务器的 URI。因此,您的其他 Web api 服务可以使用此 URI 访问您的身份服务器。

  1. 在必须使用身份服务器的网站 api 中添加 A​​uthority
services.AddAuthentication(options =>
          {
              options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
              options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
          }).AddJwtBearer(o =>
          {
              o.Authority = "http://<your_identity_container_name>";
              o.Audience = "api1"; // APi Resource Name
              o.RequireHttpsMetadata = false;
              o.IncludeErrorDetails = true;
          });