.net core 2.2 несколько схем аутентификации - PullRequest
0 голосов
/ 22 октября 2019

Я пытаюсь сделать приложение .net core web api с аутентификацией. Мне нужно реализовать аутентификацию Windows, аутентификацию cookie, аутентификацию активного каталога ldap и аутентификацию на носителе jwt. Каждая аутентификация работает нормально, но если я пытаюсь включить все вместе, она не работает так, как я ожидаю. Чтобы сделать проверку подлинности рабочих окон, я пишу пользовательский ForwardDefaultSelector следующим образом:

services.AddAuthentication(optons =>
           {
               optons.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
               optons.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
               optons.DefaultForbidScheme = CookieAuthenticationDefaults.AuthenticationScheme;
               optons.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
               optons.DefaultAuthenticateScheme = "smart";
               optons.DefaultChallengeScheme = "smart";
               optons.DefaultScheme = "smart";
               //optons.AddScheme("Windows", config => config.HandlerType = IISDefaults.)
           })
           .AddPolicyScheme("smart", "", options =>
           {
               options.ForwardDefaultSelector = context =>
               {
                   var authHeader = context.Request.Headers[HeaderNames.Authorization].FirstOrDefault();
                   if (!string.IsNullOrEmpty(authHeader))
                   {
                       if (authHeader.StartsWith(JwtBearerDefaults.AuthenticationScheme) == true)
                           return JwtBearerDefaults.AuthenticationScheme;
                       else if (authHeader.StartsWith(IISDefaults.Negotiate) == true || authHeader.StartsWith(IISDefaults.Ntlm) == true)
                           return IISDefaults.AuthenticationScheme;

                   }
                   else
                   {
                       if (context.Request.Cookies.ContainsKey(string.IsNullOrEmpty(Configuration["ApplicationName"]) ? ".Authentication.Aion" : ".Authentication." + Configuration["ApplicationName"]))
                       {
                           return CookieAuthenticationDefaults.AuthenticationScheme;
                       }
                       else if (context.Request.Cookies.ContainsKey("LDAP_AUTH"))
                       {
                           return LdapAuthenticationDefaults.AuthenticationScheme;
                       }
                       else
                       {
                           return IISDefaults.AuthenticationScheme;
                       }
                   }
                   return CookieAuthenticationDefaults.AuthenticationScheme;

               };
           })
           .AddCookie(options =>
           {
               options.Cookie.Name = string.IsNullOrEmpty(Configuration["ApplicationName"]) ? ".Authentication.Aion" : ".Authentication." + Configuration["ApplicationName"];
               //options.Cookie.HttpOnly = true;
               options.Cookie.SecurePolicy = CookieSecurePolicy.None;
               options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax;
               options.SlidingExpiration = true;
               options.LoginPath = new PathString("/Account/Login");
               options.LogoutPath = new PathString("/Account/Logout");
               options.AccessDeniedPath = new PathString("/Account/Authorize");
               var expirationDays = Configuration.GetValue<int>("DefaultExpirationDays");
               options.ExpireTimeSpan = TimeSpan.FromDays(expirationDays);
               options.Events.OnValidatePrincipal = async (context) =>
               {
                   await SecurityStampValidator.ValidatePrincipalAsync(context);
               };
               //options.Events.OnSignedIn = context =>
               //{
               //    return Task.CompletedTask;
               //};
               //options.ForwardAuthenticate = "Ldap";

           })
           .AddJwtBearer(options =>
           {
               options.RequireHttpsMetadata = false;
               //options.SaveToken
               var session = Configuration.GetSection("BearerAuthenticationConfigs");

               options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
               {
                   ValidateIssuer = false,
                   ValidateAudience = false,
                   ValidateLifetime = true,
                   ValidateIssuerSigningKey = true,
                   RequireSignedTokens = false,
                   //ValidAudience = "http://localhost:5000",
                   //ValidIssuer = "http://localhost:5000",
                   RequireExpirationTime = false,
                   IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(session.GetValue<string>("JwtSecret"))),
                   ClockSkew = TimeSpan.Zero
               };
               options.Authority = "http://localhost:5000";
               options.Configuration = new Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration
               {
                   TokenEndpoint = new PathString("/Token"),
                   AuthorizationEndpoint = new PathString("/Account/Authorize")

               };
               options.EventsType = typeof(JWTValidationEvents);
           })
           .AddScheme<LdapAuthenticationSchemeOptions, LdapAuthenticationHandler>(LdapAuthenticationDefaults.AuthenticationScheme, options =>
           {
               var configSession = Configuration.GetSection("LdapAuthenticationConfigs");

               options.LdapHost = configSession.GetValue<string>("Host", "localhost");
               options.LdapPort = configSession.GetValue<int>("Port", 389);
               var expireDays = configSession.GetValue<int?>("ExpirationDays", Configuration.GetValue<int>("DefaultExpirationDays"));
               options.Expire = expireDays.HasValue ? TimeSpan.FromDays(expireDays.Value) : (TimeSpan?)null;
               options.UserDomain = configSession.GetValue<string>("UserDomain", "");
           });

и web.config для включения аутентификации Windows в IIS:

<?xml version="1.0" encoding="utf-8"?>
<configuration>

  <!-- To customize the asp.net core module uncomment and edit the following section. 
  For more info see https://go.microsoft.com/fwlink/?linkid=838655 -->

  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <security>
        <authentication>
         <!--I need to enable anonymous authentication for token call-->
          <anonymousAuthentication enabled="true" />
          <windowsAuthentication enabled="true" />
        </authentication>
      </security>
    </system.webServer>
  </location>

</configuration>

В то время как пользовательскийПромежуточное ПО для управления аутентификацией Windows:

public class WindowsLoginMiddleware
    {
        private readonly RequestDelegate _next;
        //private readonly ILogger _logger;
        private readonly ILogger<WindowsLoginMiddleware> _logger;




        public WindowsLoginMiddleware(RequestDelegate next, ILogger<WindowsLoginMiddleware> Logger, ApplicationDbContext db, IHttpContextAccessor httpcontextaccessor)
        {
            _next = next;
            _logger = Logger;
            _db = db;
            _httpContextAccessor = httpcontextaccessor;
        }

        public async Task InvokeAsync(HttpContext context, UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
        {
            if (signInManager.IsSignedIn(context.User))
            {
                _logger.LogInformation("User already signed in");
            }
            else
            {
                if (context.User.Identity as WindowsIdentity != null)
                {
                    _logger.LogInformation($"User with Windows Login {context.User.Identity.Name} needs to sign in");
                    var windowsUsername = context.User.Identity.Name;
                    //Here I create principal claim from db application user and save on cookie for next requests
                    var identity = await WindowsAuthenticationClaimProvider.GetIdentityClaimsAsync(windowsUsername, string.Empty, true);
                    if(identity == null)
                    {
                        _logger.LogInformation($"User {context.User.Identity.Name} has no role in system");
                        context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                        await context.Response.WriteAsync("");
                        return;
                    }
                    else
                    {
                        _logger.LogDebug($"User with username {windowsUsername} successfully signed in");
                    }
                }
            }

            // Pass the request to the next middleware
            await _next(context);
        }
    }

Это работает, но мне не нравится пользовательский ForwardDefaultSelector, потому что все запросы имеют IISDefaults.AuthenticationScheme по умолчанию, и проверка подлинности Windows делает 2 вызова для получения WindowsIdentity(передайте 2 раза WindowsLoginMiddleware).

У кого-нибудь есть лучшее решение для управления этой ситуацией? Спасибо.

...