Различная схема аутентификации (Windows, Bearer) для каждого маршрута - PullRequest
0 голосов
/ 26 октября 2018

Мне нужно добавить единый вход с использованием проверки подлинности Windows в мое веб-приложение Angular для интрасети (размещенное на IIS), которое использует для проверки подлинности токен JWT Bearer.Контроллеры защищены с помощью атрибута [Authorize], и аутентификация токена JWT Bearer работает.Все контроллеры доступны по маршруту api/.

Идея состоит в том, чтобы опубликовать новый SsoController по маршруту sso/, который должен быть защищен с помощью проверки подлинности Windows и который предоставляет WindowsLoginдействие, которое возвращает действительный токен носителя для приложения.

Назад, когда я использовал веб-формы ASP.net, это было довольно легко, вам нужно было только включить проверку подлинности Windows в разделе web.config/system.webServer, отключить еев разделе system.web и затем включите его снова под тегом <location path="sso">.Таким образом, ASP.net генерировал проблемы NTLM / Negotiate только для запросов по маршруту * 1013.

Я почти все заработал - SsoController получает имя пользователя Windows и просто создает токен JWT, ноконвейер по-прежнему генерирует заголовки WWW-Authenticate: NTLM и WWW-Authenticate: Negotiate для всех HTTP 401 ответов, а не только для ответов по маршруту sso.

Как я могу сообщить конвейеру, что мне нужен только анонимный или аутентификация на предъявителя для всех api/ запросов?

Заранее спасибо за помощь.

Программа.cs

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
  WebHost.CreateDefaultBuilder(args)
    .UseStartup<Startup>()
    .UseIISIntegration();

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    // Set up data directory
    services.AddDbContext<AuthContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("AuthContext")));

    services.AddAuthentication(IISDefaults.AuthenticationScheme);
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,

                ValidIssuer = "AngularWebApp.Web",
                ValidAudience = "AngularWebApp.Web.Client",
                IssuerSigningKey = _signingKey,
                ClockSkew = TimeSpan.Zero   //the default for this setting is 5 minutes
            };
            options.Events = new JwtBearerEvents
            {
                OnAuthenticationFailed = context =>
                {
                    if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                    {
                        context.Response.Headers.Add("Token-Expired", "true");
                    }
                    return Task.CompletedTask;
                }
            };
        });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    // In production, the Angular files will be served from this directory
    services.AddSpaStaticFiles(configuration =>
    {
        configuration.RootPath = "ClientApp/dist";
    });
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseSpaStaticFiles();
    app.UseAuthentication();

    app.UseWhen(context => context.Request.Path.StartsWithSegments("/sso"),
        builder => builder.UseMiddleware<WindowsAuthMiddleware>());

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

    app.UseSpa(spa =>
    {
        // To learn more about options for serving an Angular SPA from ASP.NET Core,
        // see https://go.microsoft.com/fwlink/?linkid=864501

        spa.Options.SourcePath = "ClientApp";

        if (env.IsDevelopment())
        {
            spa.UseAngularCliServer(npmScript: "start");
        }
    });
}

WindowsAuthMiddleware.cs

public class WindowsAuthMiddleware
{
    private readonly RequestDelegate next;

    public WindowsAuthMiddleware(RequestDelegate next)
    {
        this.next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        if (!context.User.Identity.IsAuthenticated)
        {
            await context.ChallengeAsync(IISDefaults.AuthenticationScheme);
            return;
        }

        await next(context);
    }
}

web.config

<system.webServer>
  <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="true"/>
  <security>
    <authentication>
      <anonymousAuthentication enabled="true" />
      <windowsAuthentication enabled="true" />
    </authentication>
  </security>
</system.webServer>

Ответы [ 2 ]

0 голосов
/ 30 октября 2018

Итак, я провел последние несколько дней, исследуя эту проблему, и я нашел рабочее - хотя и немного хакерское - решение.

Оказывается, что основная проблема заключается в том, что IIS будет обрабатывать согласование проверки подлинности Windows для всех 401 ответов, отправленных приложением. Это то, что делается на более низком уровне, как только вы включаете проверку подлинности Windows в IIS (или в разделе system.webServer), и я не смог найти способ обойти это поведение. На самом деле я провел тест с классическим приложением веб-форм, и он работает так же - причина, по которой я никогда не замечал этого, заключается в том, что классическая проверка подлинности с помощью форм редко генерирует 401 ответ, а скорее использует перенаправления (30x), чтобы перевести пользователя на страницу входа.

Это дало мне идею: я мог бы добавить еще одно промежуточное программное обеспечение в конвейер, который переписывает ответы 401, сгенерированные инфраструктурой авторизации, в другой, редко используемый HTTP-код, и обнаружить его в моем клиентском приложении Angular, чтобы оно работало как 401 ( обновив токен доступа или запретив навигацию маршрутизатора и т. д.). Я использовал ошибку HTTP 418 «Я чайник», поскольку это существующий, но неиспользуемый код. Вот код:

ReplaceHttp401StatusCodeMiddleware.cs

public class ReplaceHttp401StatusCodeMiddleware
{
    private readonly RequestDelegate next;

    public ReplaceHttp401StatusCodeMiddleware(RequestDelegate next)
    {
        this.next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        await next(context);

        if (context.Response.StatusCode == 401)
        {
            // Replace all 401 responses, except the ones under the /sso paths
            // which will let IIS trigger the Windows Authentication mechanisms
            if (!context.Request.Path.StartsWithSegments("/sso"))
            {
                context.Response.StatusCode = 418;
                context.Response.Headers["X-Original-HTTP-Status-Code"] = "401";
            }
        }
    }
}

Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    ...

    // Enable the SSO login using Windows Authentication
    app.UseWhen(
            context => context.Request.Path.StartsWithSegments("/sso"),
            builder => builder.UseMiddleware<WindowsAuthMiddleware>());
    app.UseMiddleware<ReplaceHttp401StatusCodeMiddleware>();

    ...
}

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

Я также применил к своему коду предложение от Микаэля Дерри (Mickaël Derriey) использовать политики авторизации, потому что это делает контроллеры более чистыми, но это не обязательно для работы решения.

0 голосов
/ 29 октября 2018

Добро пожаловать в StackOverflow! Это интересный вопрос у вас здесь. Во-первых, позвольте мне заявить, что я не проверял содержание этого ответа.

Использование политик авторизации для управления источниками аутентификации

Мне нравится идея WindowsAuthMiddleware, которую вы создали, и как она условно вставляется в конвейер, если URL начинается с /sso.

MVC интегрирован с системой авторизации и предоставляет те же возможности с политиками авторизации. Результат тот же, и вам не нужно писать код низкого уровня.

Вы можете определить политики авторизации в методе ConfigureServices. В вашем случае, если я не ошибаюсь, есть две политики:

  • все запросы к /sso должны проходить проверку подлинности с проверкой подлинности Windows; и
  • все остальные запросы должны быть аутентифицированы с помощью JWT
services.AddAuthorization(options =>
{
    options.AddPolicy("Windows", new AuthorizationPolicyBuilder()
        .AddAuthenticationSchemes(IISDefaults.AuthenticationScheme)
        .RequireAuthenticatedUser()
        .Build());

    options.AddPolicy("JWT", new AuthorizationPolicyBuilder()
        .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
        .RequireAuthenticatedUser()
        .Build());
});

Затем вы можете ссылаться на эти политики по имени в атрибутах [Authorize], используемых для украшения ваших контроллеров и / или действий.

[Authorize("Windows")]
public class SsoController : Controller
{
    // Actions
}

[Authorize("JWT")]
public class ApiController : Controller
{
    // Actions
}

Это означает, что обработчик аутентификации Windows не будет работать с /api запросами, следовательно, ответы не должны содержать заголовки WWW-Authenticate: NTLM и WWW- Authenticate: Negotiate.

Снятие автоматической аутентификации всех запросов

Когда вы передаете схему аутентификации в качестве аргумента AddAuthentication, это означает, что промежуточное ПО аутентификации будет пытаться аутентифицировать каждый запрос по этой схеме.

Это полезно, когда у вас есть одна схема аутентификации, но в этом случае вы можете подумать об ее удалении, поскольку даже для запросов к /sso обработчик JWT будет анализировать запрос на токен.

Два звонка на AddAuthentication

У вас должен быть только один звонок на AddAuthentication:

  • первый устанавливает схему проверки подлинности IIS по умолчанию, поэтому обработчик должен запускаться при каждом запросе;
  • второй вызов перезаписывает эту настройку и устанавливает схему JWT в качестве схемы по умолчанию

Дайте мне знать, как вы идете!

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