如何在 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
?是的,Client 和 API 都可以调用相同的 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 个容器在同一网络中,您可以执行以下操作:
- 在您的身份服务器中添加 IssuerUri。
services.AddIdentityServer(x =>
{
x.IssuerUri = "http://<your_identity_container_name>";
})
这将设置您的身份服务器的 URI。因此,您的其他 Web api 服务可以使用此 URI 访问您的身份服务器。
- 在必须使用身份服务器的网站 api 中添加 Authority。
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;
});
我希望能够从 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
?是的,Client 和 API 都可以调用相同的 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 个容器在同一网络中,您可以执行以下操作:
- 在您的身份服务器中添加 IssuerUri。
services.AddIdentityServer(x =>
{
x.IssuerUri = "http://<your_identity_container_name>";
})
这将设置您的身份服务器的 URI。因此,您的其他 Web api 服务可以使用此 URI 访问您的身份服务器。
- 在必须使用身份服务器的网站 api 中添加 Authority。
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;
});