examen - github docker hub
¿Cómo puedo usar IdentityServer4 desde dentro y fuera de una máquina de conexión? (2)
Quiero poder autenticarme contra un servidor de identidad (STS) desde fuera y dentro de una máquina de conexión.
Estoy teniendo problemas para establecer la autoridad correcta que funcione tanto dentro como fuera del contenedor. Si configuro la autoridad en el nombre interno mcoidentityserver:5000
entonces la API puede autenticarse, pero el cliente no puede obtener un token ya que el cliente se encuentra fuera de la red de la ventana acoplable. Si configuro la autoridad en el nombre externo localhost:5000
entonces el cliente puede obtener un token pero la API no reconoce el nombre de la autoridad (porque localhost
en este caso es la máquina host).
¿Qué debo establecer la Autoridad para? O tal vez necesito ajustar la red docker?
Diagrama
La flecha roja es la parte con la que estoy teniendo problemas.
Detalle
Estoy configurando un entorno de desarrollo docker de Windows 10 que utiliza una API de ASP.NET Core (en Linux), Identity Server 4 (ASP.NET Core en Linux) y una base de datos PostgreSQL. PostgreSQL no es un problema, incluido en el diagrama para completar. Se asigna a 9876 porque también tengo una instancia de PostgreSQL que se ejecuta en el host por ahora. mco
es un nombre abreviado de nuestra empresa.
He estado siguiendo las instrucciones de Identity Server 4 para empezar a funcionar.
Código
No estoy incluyendo el docker-compose.debug.yml
porque ha ejecutado los comandos pertinentes solo para ejecutarse en 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
Esto es creado por el complemento de Visual Studio para inyectar valores extra.
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
Donde le decimos a la API que use el servidor de identidad y establezca la autoridad.
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" }
}
};
}
}
Cliente
Ejecutando en una aplicación de consola.
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));
}
Gracias por adelantado.
Asegúrese de que IssuerUri
se establece en una constante explícita. Tuvimos problemas similares con el acceso a la instancia de Identity Server por el IP / nombre de host y lo resolvimos de esta manera:
services.AddIdentityServer(x =>
{
x.IssuerUri = "my_auth";
})
PS ¿Por qué no unifica la URL de autoridad al hostname:5000
de hostname:5000
? Sí, es posible que el Cliente y la API llamen al mismo URL hostname:5000
si:
- El puerto 5000 está expuesto (veo que está bien)
- DNS se resuelve dentro del contenedor docker.
- Tiene acceso al
hostname:5000
dehostname:5000
(verifique firewalls, topología de red, etc.)
DNS es la parte más difícil. Si tiene algún problema con él, le recomiendo que intente llegar a Identity Server por su IP expuesta en lugar de resolver el hostname
de hostname
.
Para hacer que esto funcionara, necesitaba pasar dos variables de entorno en el docker-compose.yml
y configurar CORS en la instancia del servidor de identidad para que la API pudiera llamarla. La configuración de CORS está fuera del alcance de esta pregunta; esta pregunta lo cubre bien.
Docker-Componer cambios
El servidor de identidad necesita IDENTITY_ISSUER
, que es el nombre que el servidor de identidad se dará a sí mismo. En este caso, he utilizado la IP
del IP
de la ventana acoplable y el puerto del servidor de identidad.
mcoidentityserver:
image: mcoidentityserver
build:
context: ./Mco.IdentityServer
dockerfile: Dockerfile
environment:
IDENTITY_ISSUER: "http://10.0.75.1:5000"
ports:
- 5000:5000
networks:
- mconetwork
La API necesita saber dónde está la autoridad. Podemos usar el nombre de la red de la ventana acoplable para la autoridad porque la llamada no necesita salir de la red de la ventana acoplable, la API solo está llamando al servidor de identidad para verificar el token.
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
Usando estos valores en C #
Identity Server.cs
Establece el nombre del Emisor de Identidad en 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
Ahora podemos establecer la Autoridad a la variable de entorno.
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = Configuration["IDENTITY_AUTHORITY"],
RequireHttpsMetadata = false,
ApiName = "api1"
});
Inconvenientes
Como se muestra aquí, la compilación de la ventana acoplable no sería adecuada para la producción, ya que el emisor de identidad codificado es una IP local. En su lugar, necesitaría una entrada de DNS adecuada que se asignaría a la instancia de la ventana acoplable con el servidor de identidad ejecutándose en ella. Para hacer esto, crearía un archivo de reemplazo de compilación acoplable y compilaría la producción con el valor reemplazado.
Gracias a por su ayuda.
Editar
Además de esto, escribí el proceso completo de construcción de una ventana acoplable Linux + ASP.NET Core 2 + OAuth con Identity Server en mi blog.