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

时间:2017-05-11 09:28:24

标签: docker docker-compose identityserver4

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

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

我应该将权限设置为什么?或许我需要调整docker网络?

红色箭头是我遇到麻烦的部分。 Three docker containers in a network, a client and PostgreSQL Admin, their ports and a red arrow showing where I think the problem lies.

详细

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

我一直关注Identity Server 4 instructions来启动并运行。

代码

我没有包含docker-compose.debug.yml,因为它运行的命令只与在Visual Studio中运行有关。

搬运工-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

搬运工-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"]

Identity Server 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使用Identity Server并设置权限。

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?}");
    });
}

Identity Server 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!");
        });
    }
}

Identity Server 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));
}

提前致谢。

2 个答案:

答案 0 :(得分:6)

为了完成这项工作,我需要在身份服务器实例上的docker-compose.yml和setup CORS中传入两个环境变量,以便允许API调用它。建立CORS不在这个问题的范围之内; this question很好地涵盖了它。

Docker-Compose更改

身份服务器需要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#

中使用这些值

Identity 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的整个过程。

答案 1 :(得分:1)

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

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

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

  1. 在必须使用身份服务器的 Web API 中添加权威
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;
          });