Как я могу использовать IdentityServer4 изнутри и снаружи устройства докеров?
Я хочу иметь возможность аутентифицироваться от Identity Server (STS) снаружи и внутри докерной машины.
У меня возникли проблемы с настройкой правильного авторитета, который работает как внутри, так и снаружи контейнера. Если я устанавливаю полномочия на внутреннее имя mcoidentityserver:5000
, тогда API может аутентифицироваться, но клиент не может получить токен, поскольку клиент находится за пределами сети докеров. Если я установил полномочия на внешнее имя localhost:5000
, тогда клиент может получить токен, но API не распознает имя полномочий (поскольку localhost
в этом случае является хост-машиной).
Что мне нужно настроить для этого органа? Или, возможно, мне нужно настроить сеть докеров?
Диаграмма
Красная стрелка - это та часть, с которой у меня проблемы.
![Три контейнера докеров в сети, клиент и PostgreSQL Admin, их порты и красная стрелка, показывающие, где я думаю проблема лежит.]()
Деталь
Я настраиваю среду разработки докеров Windows 10, которая использует API-интерфейс ASP.NET Core (в Linux), Identity Server 4 (ASP.NET Core в Linux) и базу данных PostgreSQL. PostgreSQL не является проблемой, включенной в диаграмму для полноты. Он сопоставлен с 9876, потому что на данный момент у меня также есть экземпляр PostgreSQL, запущенный на хосте. mco
- сокращенное название нашей компании.
Я слежу за инструкциями Identity Server 4 для запуска и запуска.
код
Я не включаю 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"]
Док файл сервера удостоверений
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?}");
});
}
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" }
}
};
}
}
Client
Запуск в консольном приложении.
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));
}
Спасибо заранее.
Ответы
Ответ 1
Убедитесь, что для IssuerUri
задана явная константа. У нас были похожие проблемы с доступом к экземпляру Identity Server по IP/имени хоста, и мы решили это следующим образом:
services.AddIdentityServer(x =>
{
x.IssuerUri = "my_auth";
})
PS Почему вы не унифицируете URL hostname:5000
для hostname:5000
? Да, клиент и API могут вызывать одно и то же hostname:5000
URL hostname:5000
если:
- Порт 5000 выставлен (вижу нормально)
- DNS разрешен внутри док-контейнера.
- У вас есть доступ к
hostname:5000
(проверьте брандмауэры, топологию сети и т.д.)
DNS - самая сложная часть. Если у вас есть какие-либо проблемы с этим, я рекомендую вам попытаться связаться с Identity Server по указанному IP-адресу, а не по hostname
.
Ответ 2
Чтобы сделать это, мне нужно было передать две переменные окружения в docker-compose.yml
и настроить CORS на экземпляре сервера идентификации, чтобы API было разрешено вызывать его. Настройка CORS выходит за рамки этого вопроса; этот вопрос хорошо это освещает.
Docker-Compose изменений
Идентификационному серверу требуется IDENTITY_ISSUER
, то есть имя, которое идентификационный сервер даст сам. В этом случае я использовал 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 должен знать, где находятся полномочия. Мы можем использовать имя сети докера для полномочий, потому что вызов не должен выходить за пределы сети докера, 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
Используя эти значения в С#
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
Теперь мы можем установить Authority для переменной среды.
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = Configuration["IDENTITY_AUTHORITY"],
RequireHttpsMetadata = false,
ApiName = "api1"
});
Недостатки
Как показано здесь, docker-compose не годится для производства, так как издатель идентификаторов с жестким кодом является локальным IP. Вместо этого вам понадобится правильная запись DNS, которая будет сопоставлена с экземпляром докера с запущенным на нем сервером идентификации. Для этого я бы создал файл переопределения docker-compose и собрал бы производство с переопределенным значением.
Спасибо Илья-Чумакову за помощь.
редактировать
В дополнение к этому я написал весь процесс создания докера Linux + ASP.NET Core 2 + OAuth с Identity Server в своем блоге.