web-dev-qa-db-fra.com

Comment implémenter globalement l'autorisation basée sur le groupe AD dans l'application web asp.net core 2.x?

Je me demande si quelqu'un pourrait me pointer une direction ou un exemple qui a le code complet pour moi d'avoir une idée globale?

Merci.

pdate: Je n'ai que le morceau de code suivant dans Startup.cs et je m'assure que windowsAutication est vrai dans launchSettings.json.

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

    services.AddMvc(config =>
    {
        var policy = new AuthorizationPolicyBuilder()
                         .RequireAuthenticatedUser()
                         //.RequireRole(@"Departmental - Information Technology - Development")   // Works
                         .RequireRole(@"*IT.Center of Excellence.Digital Workplace")              // Error
                         .Build();
        config.Filters.Add(new AuthorizeFilter(policy));
    });

}

Je suppose que j'ai activé l'authentification et essaie d'autoriser les utilisateurs qui font partie du groupe AD spécifié à accéder à l'application au niveau global.

Si j'utilise le RequireRole commenté, cela fonctionne, mais que j'utilise le RequireRole non commenté, cela me donne cette erreur: Win32Exception: la relation d'approbation entre le domaine principal et le domaine approuvé a échoué.

La ligne supérieure de la pile affiche: System.Security.Principal.NTAccount.TranslateToSids (IdentityReferenceCollection sourceAccounts, out bool someFailed)

Une idée pourquoi?

Ma compréhension de la mise à jour ci-dessus

Il semble que le nom de groupe spécifié dans RequireRole soit une liste de distribution de courrier électronique et non un groupe de sécurité. Si j'utilise un autre groupe AD, cela fonctionne, mais avec cette nouvelle erreur:

InvalidOperationException: aucun authenticationScheme n'a été spécifié et aucun DefaultForbidScheme n'a été trouvé.

Si j'ajoute IIS default authenticationScheme dans ConfigureServices dans Startup.cs

services.AddAuthentication(IISDefaults.AuthenticationScheme);

cela me donne une page HTTP 403: Le site Web a refusé d'afficher cette page Web

Voici donc le code final:

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

    services.AddAuthentication(IISDefaults.AuthenticationScheme);

    services.AddMvc(config =>
    {
        var policy = new AuthorizationPolicyBuilder()
                         .RequireAuthenticatedUser()
                         .RequireRole(@"Departmental - Information Technology - Development") // AD security group
                         .Build();
        config.Filters.Add(new AuthorizeFilter(policy));
    });

}

Corrigez-moi si je comprends mal. Je vous remercie.

6
Xiao Han

Option 1: authentification Windows

Vous pouvez activer l'authentification Windows pour les applications intranet. Lisez les documents ici . Vous pouvez vérifier si un utilisateur est dans un rôle/groupe en faisant quelque chose comme this .

Avant de le faire, vous pouvez vérifier les informations sur les groupes auxquelles votre ordinateur s'est joint en exécutant gpresult /R Dans l'invite de commande. Voir cet article pour plus d'informations.

User.IsInRole("xxxx")  // this should return True for any group listed up there

Vous n'avez pas besoin de convertir le principal actuel en principal Windows si vous n'avez pas besoin d'obtenir d'informations sur Windows.

Si vous souhaitez obtenir une liste de tous les groupes, vous devez toujours interroger votre AD.

avertissement:

Parfois, je vois que certains groupes n'apparaissent pas dans le résultat en utilisant gpresult /R Sur l'ordinateur, par rapport à la méthode de l'option 2. C'est pourquoi parfois quand vous faites User.IsInRole() et cela retourne false. Je ne sais toujours pas pourquoi cela se produit.

Option 2: authentification de formulaire avec recherche AD

L'authentification Windows offre juste quelques informations sur l'utilisateur et les groupes AD. Parfois c'est suffisant mais la plupart du temps ce n'est pas le cas.

Vous pouvez également utiliser l'authentification par formulaire régulière et parler à l'AD en dessous et émettre un cookie. De cette façon, même si l'utilisateur doit se connecter à votre application à l'aide de ses informations d'identification et de son mot de passe Windows, vous avez le contrôle total sur les informations AD.

Vous ne voulez pas tout écrire à la main. Heureusement, il existe une bibliothèque Novell.Directory.Ldap.NETStandard pour vous aider. Vous pouvez le trouver dans NuGet.

Interfaces pour définir ce dont vous avez besoin de l'AD, ainsi que le protocole de connexion:

namespace DL.SO.Services.Core
{
    public interface IAppUser
    {
        string Username { get; }
        string DisplayName { get; }
        string Email { get; }
        string[] Roles { get; }
    }

    public interface IAuthenticationService
    {
        IAppUser Login(string username, string password);
    }
}

Implémentation AppUser:

using DL.SO.Services.Core;

namespace DL.SO.Services.Security.Ldap.Entities
{
    public class AppUser : IAppUser
    {
        public string Username { get; set; }
        public string DisplayName { get; set; }
        public string Email { get; set; }
        public string[] Roles { get; set; }
    }
}

Objet de configuration LDAP pour le mappage des valeurs de appsettings.json:

namespace DL.SO.Services.Security.Ldap
{
    public class LdapConfig
    {
        public string Url { get; set; }
        public string BindDn { get; set; }
        public string Username { get; set; }
        public string Password { get; set; }
        public string SearchBase { get; set; }
        public string SearchFilter { get; set; }
    }
}

Implémentation de LdapAuthenticationService:

using Microsoft.Extensions.Options;
using Novell.Directory.Ldap;
using System;
using System.Linq;
using System.Text.RegularExpressions;
using DL.SO.Services.Core;
using DL.SO.Services.Security.Ldap.Entities;

namespace DL.SO.Services.Security.Ldap
{
    public class LdapAuthenticationService : IAuthenticationService
    {
        private const string MemberOfAttribute = "memberOf";
        private const string DisplayNameAttribute = "displayName";
        private const string SAMAccountNameAttribute = "sAMAccountName";
        private const string MailAttribute = "mail";

        private readonly LdapConfig _config;
        private readonly LdapConnection _connection;

        public LdapAuthenticationService(IOptions<LdapConfig> configAccessor)
        {
            _config = configAccessor.Value;
            _connection = new LdapConnection();
        }

        public IAppUser Login(string username, string password)
        {
            _connection.Connect(_config.Url, LdapConnection.DEFAULT_PORT);
            _connection.Bind(_config.Username, _config.Password);

            var searchFilter = String.Format(_config.SearchFilter, username);
            var result = _connection.Search(
                _config.SearchBase,
                LdapConnection.SCOPE_SUB, 
                searchFilter,
                new[] { 
                    MemberOfAttribute, 
                    DisplayNameAttribute, 
                    SAMAccountNameAttribute, 
                    MailAttribute 
                }, 
                false
            );

            try
            {
                var user = result.next();
                if (user != null)
                {
                    _connection.Bind(user.DN, password);
                    if (_connection.Bound)
                    {
                        var accountNameAttr = user.getAttribute(SAMAccountNameAttribute);
                        if (accountNameAttr == null)
                        {
                            throw new Exception("Your account is missing the account name.");
                        }

                        var displayNameAttr = user.getAttribute(DisplayNameAttribute);
                        if (displayNameAttr == null)
                        {
                            throw new Exception("Your account is missing the display name.");
                        }

                        var emailAttr = user.getAttribute(MailAttribute);
                        if (emailAttr == null)
                        {
                            throw new Exception("Your account is missing an email.");
                        }

                        var memberAttr = user.getAttribute(MemberOfAttribute);
                        if (memberAttr == null)
                        {
                            throw new Exception("Your account is missing roles.");
                        }

                        return new AppUser
                        {
                            DisplayName = displayNameAttr.StringValue,
                            Username = accountNameAttr.StringValue,
                            Email = emailAttr.StringValue,
                            Roles = memberAttr.StringValueArray
                                .Select(x => GetGroup(x))
                                .Where(x => x != null)
                                .Distinct()
                                .ToArray()
                        };
                    }
                }
            }
            finally
            {
                _connection.Disconnect();
            }

            return null;
        }

        private string GetGroup(string value)
        {
            Match match = Regex.Match(value, "^CN=([^,]*)");
            if (!match.Success)
            {
                return null;
            }

            return match.Groups[1].Value;
        }
    }
}

Configuration dans appsettings.json (juste un exemple):

{
    "ldap": {
       "url": "[YOUR_COMPANY].loc",
       "bindDn": "CN=Users,DC=[YOUR_COMPANY],DC=loc",
       "username": "[YOUR_COMPANY_ADMIN]",
       "password": "xxx",
       "searchBase": "DC=[YOUR_COMPANY],DC=loc",
       "searchFilter": "(&(objectClass=user)(objectClass=person)(sAMAccountName={0}))"
    },
    "cookies": {
        "cookieName": "cookie-name-you-want-for-your-app",
        "loginPath": "/account/login",
        "logoutPath": "/account/logout",
        "accessDeniedPath": "/account/accessDenied",
        "returnUrlParameter": "returnUrl"
    }
}

Configurez l'authentification (peut-être également l'autorisation) pour l'application:

namespace DL.SO.Web.UI
{
    public class Startup
    {
        private readonly IHostingEnvironment _currentEnvironment;
        public IConfiguration Configuration { get; private set; }

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

        public void ConfigureServices(IServiceCollection services)
        { 
            // Authentication service
            services.Configure<LdapConfig>(this.Configuration.GetSection("ldap"));
            services.AddScoped<IAuthenticationService, LdapAuthenticationService>();

            // MVC
            services.AddMvc(config =>
            {
                // Requiring authenticated users on the site globally
                var policy = new AuthorizationPolicyBuilder()
                    .RequireAuthenticatedUser()

                    // You can chain more requirements here
                    // .RequireRole(...) OR
                    // .RequireClaim(...) OR
                    // .Requirements.Add(...)         

                    .Build();
                config.Filters.Add(new AuthorizeFilter(policy));
            });

            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

            // Authentication
            var cookiesConfig = this.Configuration.GetSection("cookies")
                .Get<CookiesConfig>();
            services.AddAuthentication(
                CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options =>
                {
                    options.Cookie.Name = cookiesConfig.CookieName;
                    options.LoginPath = cookiesConfig.LoginPath;
                    options.LogoutPath = cookiesConfig.LogoutPath;
                    options.AccessDeniedPath = cookiesConfig.AccessDeniedPath;
                    options.ReturnUrlParameter = cookiesConfig.ReturnUrlParameter;
                });

            // Setup more authorization policies as an example.
            // You can use them to protected more strict areas. Otherwise
            // you don't need them.
            services.AddAuthorization(options =>
            {
                options.AddPolicy("AdminOnly", 
                    policy => policy.RequireClaim(ClaimTypes.Role, "[ADMIN_ROLE_OF_YOUR_COMPANY]"));

                // More on Microsoft documentation
                // https://docs.Microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-2.1
            });
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseAuthentication();
            app.UseMvc(...);
        }  
    }
}

Comment authentifier les utilisateurs à l'aide du service d'authentification:

namespace DL.SO.Web.UI.Controllers
{
    public class AccountController : Controller
    {
        private readonly IAuthenticationService _authService;

        public AccountController(IAuthenticationService authService)
        {
            _authService = authService;
        }

        [AllowAnonymous]
        [HttpPost]
        public async Task<IActionResult> Login(LoginViewModel model)
        {
            if (ModelState.IsValid)
            {
                try
                {
                    var user = _authService.Login(model.Username, model.Password);

                    // If the user is authenticated, store its claims to cookie
                    if (user != null)
                    {
                        var userClaims = new List<Claim>
                        {
                            new Claim(ClaimTypes.Name, user.Username),
                            new Claim(CustomClaimTypes.DisplayName, user.DisplayName),
                            new Claim(ClaimTypes.Email, user.Email)
                        };

                        // Roles
                        foreach (var role in user.Roles)
                        {
                            userClaims.Add(new Claim(ClaimTypes.Role, role));
                        }

                        var principal = new ClaimsPrincipal(
                            new ClaimsIdentity(userClaims, _authService.GetType().Name)
                        );

                        await HttpContext.SignInAsync(                            
                          CookieAuthenticationDefaults.AuthenticationScheme, 
                            principal,
                            new AuthenticationProperties
                            {
                                IsPersistent = model.RememberMe
                            }
                        );

                        return Redirect(Url.IsLocalUrl(model.ReturnUrl)
                            ? model.ReturnUrl
                            : "/");
                    }

                    ModelState.AddModelError("", @"Your username or password
                        is incorrect. Please try again.");
                }
                catch (Exception ex)
                {
                    ModelState.AddModelError("", ex.Message);
                }
            }
            return View(model);
        }
    }
}

Comment lire les informations stockées dans les revendications:

public class TopNavbarViewComponent : ViewComponent
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public TopNavbarViewComponent(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public async Task<IViewComponentResult> InvokeAsync()
    {
        string loggedInUsername = _httpContextAccessor.HttpContext.User.Identity.Name;

        string loggedInUserDisplayName = _httpContextAccessor.HttpContext.User.GetDisplayName();

       ...
       return View(vm);
    }
}

Méthode d'extension pour ClaimsPrincipal:

namespace DL.SO.Framework.Mvc.Extensions
{
    public static class ClaimsPrincipalExtensions
    {
        public static Claim GetClaim(this ClaimsPrincipal user, string claimType)
        {
            return user.Claims
                .SingleOrDefault(c => c.Type == claimType);
        }

        public static string GetDisplayName(this ClaimsPrincipal user)
        {
            var claim = GetClaim(user, CustomClaimTypes.DisplayName);

            return claim?.Value;
        }

        public static string GetEmail(this ClaimsPrincipal user)
        {
            var claim = GetClaim(user, ClaimTypes.Email);

            return claim?.Value;
        }
    }
}

Comment utiliser l'autorisation de stratégie:

namespace DL.SO.Web.UI.Areas.Admin.Controllers
{
    [Area("admin")]
    [Authorize(Policy = "AdminOnly")]
    public abstract class AdminControllerBase : Controller {}
}

Prime

Vous pouvez télécharger le AD Explorer de Microsoft afin de visualiser l'AD de votre entreprise.

Opps. Je comptais simplement donner quelque chose pour une longueur d'avance mais j'ai fini par écrire un très long post.

8
David Liang