Невозможно вызвать API, используя мой идентификационный сервер из собственного приложения - PullRequest
0 голосов
/ 21 января 2019

Я хотел бы сделать Identity-сервер и API в двух отдельных докерах для собственного приложения (клиентского мобильного). Он работает на обратном прокси-сервере NGINX и Let's Encrypt.

                               Dockers
                     ---------------------------
                    |       Reverse Proxy       |
                    |  -----------------------  |
   --------         | |    ----------------   | |
  | Mobile | ---------|-> | IdendityServer |  | |
   --------         | |   |   Port: 5000   |  | |
       |            | |    ----------------   | |
       |            | |            |          | |
       |            | |    ----------------   | |
       ---------------|-> |      API       |  | |
                    | |   |   Port: 5001   |  | |
                    | |    ----------------   | |
                    |  -----------------------  |
                    |                           |
                    |      ----------------     |
                    |     |   PostgreSQL   |    |
                    |     |   Port: 5432   |    |
                    |      ----------------     |
                     ---------------------------

С моей текущей конфигурацией:

  • Обратный прокси с Let's Encrypt хорошо работает с мобильного телефона
  • API вызова без [Authority] хорошо работает с мобильного телефона
  • Соединение Identity Server с гибридным потоком работает, и в списке претензий моего пользователя указан

Мои коды ниже.

IckerityServer Dockerfile

FROM microsoft/dotnet:2.0-sdk
COPY is4/* /app/
WORKDIR /app

ENV ASPNETCORE_URLS http://*:5000
EXPOSE 5000

ENTRYPOINT ["dotnet", "IdentityServer.dll"]

API Dockerfile

FROM microsoft/dotnet:2.0-sdk
COPY api/* /app/
WORKDIR /app

ENV ASPNETCORE_URLS http://*:5001
EXPOSE 5001

ENTRYPOINT ["dotnet", "ApiServer.dll"]

DockerCompose

version: '3'

services:
  identityserver:
    image: identityserver
    build:
      context: .
      dockerfile: IdentityServer/Dockerfile
    container_name: ids
    restart: always
    ports:
      - 5000:5000
#    expose:
#      - "5000"
    environment:
      ASPNETCORE_ENVIRONMENT: Development
      VIRTUAL_PORT: 5000
      VIRTUAL_HOST: ids.mydomain.com
      LETSENCRYPT_HOST: ids.mydomain.com
      LETSENCRYPT_EMAIL: myuser@mydomain.com
      IDENTITY_ISSUER: "https://ids.mydomain.com"
      IDENTITY_REDIRECT: "com.mobiletest.nativeapp"
      IDENTITY_CORS_ORIGINS: "https://ids.mydomain.com"
    depends_on:
      - db
  apiserver:
    image: apiserver
    build:
      context: .
      dockerfile: ApiServer/Dockerfile
    container_name: api
    restart: always
    ports:
      - 5001:5001
#    expose:
#      - "5001"
    environment:
      ASPNETCORE_ENVIRONMENT: Development
      VIRTUAL_PORT: 5001
      VIRTUAL_HOST: api.mydomain.com
      LETSENCRYPT_HOST: api.mydomain.com
      LETSENCRYPT_EMAIL: myuser@mydomain.com
      IDENTITY_AUTHORITY: "http://identityserver:5000"
      CLIENT_CORS_ORIGINS: "com.mobiletest.nativeapp"
    depends_on:
      - identityserver
      - db
    links:
      - identityserver
  db:
    image: postgresql:10
    build:
      context: .
      dockerfile: PostgreSQL/Dockerfile
    container_name: db
    restart: always
    ports:
      - "5432:5432"
    volumes:
      - /www/database:/var/lib/postgresql/data
    environment:
      - PGDATA=/var/lib/postgresql/data/pgdata

networks:
    default:
       external:
         name: nginx-proxy

Код запуска IdentityServer

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));
    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    // Add application services.
    services.AddTransient<IEmailSender, EmailSender>();

    services.AddMvc();

    // Configure identity server with in-memory stores, keys, clients and scopes
    services.AddIdentityServer(opt =>
    {
        opt.IssuerUri = Configuration["IDENTITY_ISSUER"];
        opt.PublicOrigin = Configuration["IDENTITY_ISSUER"];
    })
    .AddCorsPolicyService<InMemoryCorsPolicyService>() // Add the CORS service
    .AddDeveloperSigningCredential()
    .AddInMemoryPersistedGrants()
    .AddInMemoryIdentityResources(Config.GetIdentityResources())
    .AddInMemoryApiResources(Config.GetApiResources())
    .AddInMemoryClients(Config.GetClients())
    .AddAspNetIdentity<ApplicationUser>();

    services.AddAuthentication();

    // preserve OIDC state in cache (solves problems with AAD and URL lenghts)
    services.AddOidcStateDataFormatterCache("aad");

    // add CORS policy for non-IdentityServer endpoints
    services.AddCors(options =>
    {
        options.AddPolicy("CorsPolicy", policy =>
        {
            policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod();
        });
    });
} // ConfigureServices()

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseBrowserLink();
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();

    app.UseIdentityServer();

    app.UseForwardedHeaders(new ForwardedHeadersOptions
    {
        ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
    });

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

Код конфигурации IdentityServer

public static IEnumerable<IdentityResource> GetIdentityResources()
{
    return new List<IdentityResource>
    {
        new IdentityResources.OpenId(),
        new IdentityResources.Profile()
    };
}

public static IEnumerable<ApiResource> GetApiResources()
{
    return new List<ApiResource>
    {
        new ApiResource("api1", "My API")
        {
            ApiSecrets = { new Secret("secret".Sha256()) }
        }
    };
}

public static IEnumerable<Client> GetClients()
{
    // client credentials client
    return new List<Client>
    {
        new Client
        {
            ClientId = "native.hybrid",
            ClientName = "Native Client (Hybrid with PKCE)",
            AllowedGrantTypes = GrantTypes.Hybrid,
            RequirePkce = true,
            RequireConsent = false,
            //RequireClientSecret = false,
            ClientSecrets = { new Secret("secret".Sha256()) },                   
            RedirectUris = { Configuration["IDENTITY_REDIRECT"] + "://signin-oidc" },
            PostLogoutRedirectUris = { Configuration["IDENTITY_REDIRECT"] + "://signout-callback-oidc" },
            AllowedScopes = { "openid", "profile" },
            AllowedCorsOrigins = { Configuration["IDENTITY_CORS_ORIGINS"] },
            AllowOfflineAccess = true,
            //AllowAccessTokensViaBrowser = true
            RefreshTokenUsage = TokenUsage.ReUse
        }
    };
} // GetClients()

Api Config code

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvcCore()
        .AddAuthorization()
        .AddJsonFormatters();
    if (Configuration["CLIENT_CORS_ORIGINS"] == "")
    {
        services.AddCors(options =>
        {
            options.AddPolicy("CorsPolicy",
                builder => builder
                .AllowAnyMethod()
                .AllowAnyOrigin()
                .AllowAnyHeader());
        });
    }
    else
    {
        services.AddCors(options =>
        {
            options.AddPolicy("CorsPolicy",
                builder => builder
                .AllowAnyHeader()
                .AllowAnyMethod()
                .WithOrigins(Configuration["CLIENT_CORS_ORIGINS"]));
        });
    }
    services.AddAuthentication("Bearer");
    services.AddAuthentication(options => //adds the authentication services to DI
    {
        //We are using a cookie as the primary means to authenticate a user (via “Cookies” as the DefaultScheme). We set the DefaultChallengeScheme to “oidc” because when we need the user to login, we will be using the OpenID Connect scheme.
        options.DefaultScheme = "Cookies";
        options.DefaultChallengeScheme = "oidc";
    })
        .AddCookie("Cookies")       //add the handler that can process cookies
        .AddOpenIdConnect("oidc", options => //configure the handler that perform the OpenID Connect protocol
        {
            options.SignInScheme = "Cookies"; //is used to issue a cookie using the cookie handler once the OpenID Connect protocol is complete
            options.Authority = Configuration["IDENTITY_AUTHORITY"]; //indicates that we are trusting IdentityServer
            options.RequireHttpsMetadata = false;
            options.ClientId = "native.hybrid";
            options.SaveTokens = true;
            options.ClientSecret = "secret"; //used to persist the tokens from IdentityServer in the cookie
            options.ResponseType = "code id_token";
        });

    services.AddMvc();
} // ConfigureServices()

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseAuthentication();

    app.UseCors("CorsPolicy");

    app.UseForwardedHeaders(new ForwardedHeadersOptions
    {
        ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
    });

    app.UseMvc();
} // Configure()

Код контроллера API

[Route("api/[controller]")]
[EnableCors("CorsPolicy")]
[Authorize]
public class ValuesController : Controller
{
    // GET api/values
    [HttpGet]
    public IEnumerable<string> Get()
    {
        return new string[] { "testvalue1", "testvalue2" };
    }
} 

на Xamarin для клиента мобильного

var options = new OidcClientOptions
{
    Authority = "https://ids4.syladebox.com",
    ClientId = "native.hybrid",
    ClientSecret = "secret",
    //Scope = "openid profile api1 offline_access",
    Scope = "openid profile offline_access",
    ResponseMode = OidcClientOptions.AuthorizeResponseMode.Redirect,

    RedirectUri = "com.mobiletest.nativeapp://signin-oidc",
    PostLogoutRedirectUri = "com.mobiletest.nativeapp://signout-callback-oidc",

    //Flow = OidcClientOptions.AuthenticationFlow.Hybrid,
    //Policy = policy,

    //Browser = new SFAuthenticationSessionBrowser()
    // new in iOS 12
    Browser = new ASWebAuthenticationSessionBrowser()
    //Browser = new PlatformWebView()
};

_client = new OidcClient(options); 

var result = await _client.LoginAsync(new LoginRequest());

if (result.IsError)
{
    OutputText.Text = result.Error;
    return;
}

if (result.AccessToken != null)
{
    var client = new HttpClient();
    client.SetBearerToken(result.AccessToken);
    var response = await client.GetAsync("https://api.mydomain.com/api/values");
    if (!response.IsSuccessStatusCode) 
    {
        OutputText.Text = response.ReasonPhrase;
        return;
    }

    var content = await response.Content.ReadAsStringAsync();
    OutputText.Text = JArray.Parse(content).ToString();
}

Проблема не решена:

Проблема в том, что невозможно вызвать API от имени моего пользователя. Он возвращает либо либо «Нет авторизованного», либо «Bad gateway» после:

response = await client.GetAsync("https://api.mydomain.com/api/values"); 

Эти ошибки зависят от переменных среды в докерах IdentityServer и Api.

Мои текущие переменные среды:

IDENTITY_ISSUER: "https://ids.mydomain.com"

IDENTITY_REDIRECT: "com.mobiletest.nativeapp"

IDENTITY_CORS_ORIGINS: "https://ids.mydomain.com"

IDENTITY_AUTHORITY: "http://identityserver:5000"

CLIENT_CORS_ORIGINS: "com.mobiletest.nativeapp"

Вызов API (https://api.mydomain.com/api/values) возвращает «Bad gateway».

Я думаю, что IDENTITY_ISSUER, IDENTITY_REDIRECT являются исправлениями, потому что соединение с Identity Server успешно.

Проблема связана с другими переменными среды (IDENTITY_CORS_ORIGINS, IDENTITY_AUTHORITY и CLIENT_CORS_ORIGINS) или кодами Identity Server / API?

Обновление 26 января:

Чтобы убедиться, что моя программа API работает, я переделал программу API до самого простого:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            services.AddAuthentication("Bearer")
                .AddIdentityServerAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme, options =>
                 {
                     options.Authority = Configuration["IDENTITY_AUTHORITY"];
                     options.ApiName = "api";
                     //options.ApiSecret = "secret";
                 });

            // Add CORS policy for non-IdentityServer endpoints
            services.AddCors(options =>
            {
                options.AddPolicy("api", policy =>
                {
                    policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod();
                });
            });
        } // ConfigureServices()

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseAuthentication();

            app.UseCors("api");

            app.UseForwardedHeaders(new ForwardedHeadersOptions
            {
                ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
            });

            app.UseMvc();
        } // Configure()

с контроллером API:

    [Route("api/[controller]")]
    [Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme)]
    public class ValuesController : Controller
    {
        // GET api/values
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { "testvalue1", "testvalue2" };
        }
     }

и я использую в своем первом тесте другой сервер идентификации:

demo.identityserver.io

Для этого теста я делаю следующую конфигурацию:

  apiserver:
    ...
    ports:
      - 5001:80
    environment:
      ...
      IDENTITY_AUTHORITY: "https://demo.identityserver.io"
      #CLIENT_CORS_ORIGINS (omitted in the code)

Мои OidcClientOptions в коде клиента:

            var options = new OidcClientOptions
            {
                Authority = "https://demo.identityserver.io",
                ClientId = "native.hybrid",
                Scope = "openid profile email api offline_access",
                ResponseMode = OidcClientOptions.AuthorizeResponseMode.Redirect,

                RedirectUri = "com.mobiletest.nativeapp://callback",
                PostLogoutRedirectUri = "com.mobiletest.nativeapp://callback",
                Browser = new ASWebAuthenticationSessionBrowser()
            };

Функция входа в систему - в моей первой теме.

  • Соединение сервера идентификации с гибридным потоком работает
  • Вызов API успешен!

Поскольку demo.identityserver.io является демонстрационным сервером идентификации, я сомневаюсь, что он работает как производственный вариант, тогда я протестировал другой сервер идентификации (Okta) с той же программой API:

dev-xxxxxx.okta.com

Для этого теста я делаю следующую конфигурацию:

  apiserver:
    ...
    ports:
      - 5001:80
    environment:
      ...
      IDENTITY_AUTHORITY: "https://dev-xxxxxx.okta.com"
      #CLIENT_CORS_ORIGINS (omitted in the code)

Мои OidcClientOptions в коде клиента:

            var options = new OidcClientOptions
            {
                Authority = "https://dev-xxxx.okta.com",
                ClientId = "xxxxxxxxxxxxxxxxxxx", // ClientId is hidden in this topic

                Scope = "openid profile email offline_access",
                ResponseMode = OidcClientOptions.AuthorizeResponseMode.Redirect,

                RedirectUri = "com.okta.dev-xxxxxx:/callback",
                PostLogoutRedirectUri = "com.okta.dev-xxxxxx:/callback",

                Browser = new ASWebAuthenticationSessionBrowser()
            };
  • Соединение сервера идентификации с гибридным потоком работает
  • Вызов API не работает, он возвращает сообщение «Unauthorized».

С помощью двух тестов я не могу определить, хорошо ли работает моя программа API.

Не могли бы вы помочь мне? Большое спасибо!

Ответы [ 4 ]

0 голосов
/ 27 января 2019

На прошлой неделе Nginx работал плохо. Вот почему у меня появилось сообщение «Bad Gateway», когда я начал вызов API. Я перезапускаю его, и сообщение об ошибке становится «Внутренняя ошибка сервера»

Теперь

1. Обратный прокси

С удалением [авторизовать], https://api.mydomain.com/api/values работает и хорошо дает:

0   "testvalue1"
1   "testvalue2"

https://ids.mydomain.com/.well-known/openid-configuration также работает. Я думаю, что Nginx и Let's Encrypt хорошо работают.

Вы согласны?

2. Переменные среды

Я думал об этом и проверил переменные окружения, и я уверен, что они работают.

Я не хочу жестко программировать свою конфигурацию для быстрого тестирования, чтобы избежать перекомпиляции кода и загрузки его на мой VPS.

Переменные среды, они должны исходить из этого кода:

        public Startup(IHostingEnvironment env, IConfiguration configuration)
        {
            Configuration = configuration;
        }

в Startup.cs

3. Настройки запуска

Идентификационный сервер

    "IdentityServer": {
      "commandName": "Project",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "applicationUrl": "http://localhost:5000/"
    }

Api

    "Api": {
      "commandName": "Project",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "applicationUrl": "http://localhost:5001/"
    }

Мои переменные окружения, используемые в модуле компоновки, не используются в настройках запуска и не перезаписываются.

4. Открытые порты

У меня в файлах Dockerfile

IdentityServer:

ENV ASPNETCORE_URLS http://*:5000
EXPOSE 5000

и Api:

ENV ASPNETCORE_URLS http://*:5001
EXPOSE 5001

В файле Docker-compose у меня есть две возможные конфигурации:

  identityserver:
    ...
    ports:
      - 5000:80
...
  api:
    ...
    ports:
      - 5001:80
    environment:
      ...
      IDENTITY_AUTHORITY: "http://identityserver"

Или

  identityserver:
...
    ports:
      - 5000:5000
  ...
  api:
    ...
    ports:
      - 5001:5001
    environment:
      ...
      IDENTITY_AUTHORITY: "http://identityserver:5000"

Эти две конфигурации работают одинаково.

Вы согласны?

Должен ли я использовать https в сети Docker?

Я опубликую здесь тему для моих тестов с двумя другими серверами идентификации: demo.identityserver.io и dev-xxxxxx.okta.com.

Спасибо за помощь.

0 голосов
/ 22 января 2019

Я сделал ошибку: ids.mydomain.com вместо ids4.syladebox.com.

Да, я использую https для ids.mydomain.com и проверил http в докере API с помощью команды:

docker exec -it api sh

и

curl -i -H "Accept: application/json" "http://identityserver:5000/.well-known/openid-configuration"

не работаетно

curl -i -H "Accept: application/json" "identityserver:5000/.well-known/openid-configuration"

успешно (без http).

Я изменяю "http://identityserver:5000" на" identityServer: 5000 "в переменной среды IDENTITY_AUTHORITY, и вызов APi по-прежнему возвращает" Bad Gateway "из мобильного приложения

0 голосов
/ 26 января 2019

У вас много чего происходит, и, скорее всего, вам нужно решить несколько проблем. Вот некоторые вещи, которые я заметил.

Сначала вы пытаетесь получить доступ к https://api.mydomain.com/api/values (что означает https://api.mydomain.com:443/api/values), но вы не выставили 443. Вместо этого вы выставили http://api.mydomain.com:5001. Исправьте Uri, который пытается получить ваш клиент чтобы получить доступ первым.

Тогда я бы начал с того, чтобы вынуть из состава NGINX и прокомментировать нашу конфигурацию Identity Server в вашем API, и убедиться, что у вас есть доступ к веб-API.

Как только вы сможете получить доступ к веб-API, вы можете поочередно возвращать NGINX и Identity Server в микшер и посмотреть, не вызывают ли они проблемы.

После этого:

  1. Просмотрите ваш код и убедитесь, что имена ваших хостов совпадают. Например, я вижу несколько разных URL / имен хостов, используемых для указания на ваш сервер идентификации. - http://identityserver:5000, https://ids.mydomain.com, https://ids4.syladebox.com. Убедитесь, что все они используют одну и ту же схему http (http или https), одно и то же имя хоста и один и тот же порт.
  2. Настройте метод API для возврата переменных конфигурации, чтобы увидеть, для чего они устанавливаются. Я не вижу ссылки на ConfigurationBuilder.AddEnvironmentVariables(), поэтому мне интересно, не установлены ли они и, следовательно, пустые в ваших вызовах конфигурации. С другой стороны, поскольку у вас есть проблемы с конфигурацией, вы можете захотеть жестко закодировать свою конфигурацию с помощью волшебных строк, и, когда у вас все заработает, переместите их в переменные конфигурации / среды.
  3. Убедитесь, что переменные среды не перезаписываются, например, настройками запуска .
  4. Не уверен, как у вас настроен NGNIX, но вам может понадобиться выставить https://:[port] в ваших файлах Docker, как это. ENV ASPNETCORE_URLS https://*:5000 И ваш Identity Server, и API, и просто разоблачение http://:[port]
0 голосов
/ 21 января 2019

Вы пытались использовать .AddJwtBearer() в своем API вместо .AddAuthentication("Bearer")?

Примерно так:

services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =>
    {
        options.Authority = "http://localhost:5000";
        options.RequireHttpsMetadata = false;
        options.Audience = "api1";
    });

Источник: http://docs.identityserver.io/en/latest/quickstarts/1_client_credentials.html

Хотя источник ссылается на учетные данные клиента, я думаю, что вы, вероятно, захотите использовать токены Jwt независимо от типа клиента.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...