Я пытаюсь сделать приложение .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
).
У кого-нибудь есть лучшее решение для управления этой ситуацией? Спасибо.