繁体   English   中英

如何在ASP.NET Core 2.x Web应用程序中全局实现基于AD组的授权?

[英]How to implement AD Group based authorization globally in asp.net core 2.x web application?

我想知道是否有人可以为我指明一个方向或一个例子,使我获得完整的代码以得到一个总体思路?

谢谢。

更新:我在Startup.cs中仅包含以下代码,并确保launchSettings.json中的windowsAutication为true。

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));
    });

}

我想我已启用身份验证,并尝试授权指定AD组中的用户有权访问全局级别的应用程序。

如果我使用注释的RequireRole可以正常工作,但使用未注释的RequireRole则会给我这个错误:Win32Exception:主域和受信任域之间的信任关系失败。

堆栈的第一行显示:System.Security.Principal.NTAccount.TranslateToSids(IdentityReferenceCollection sourceAccounts,在bool someFailed外)

知道为什么吗?

从上面的更新我的理解

似乎在RequireRole中指定的组名称是电子邮件通讯组列表而不是安全组。 如果我使用其他一些AD组,则可以正常工作,但会出现以下新错误:

InvalidOperationException:未指定authenticationScheme,也未找到DefaultForbidScheme。

如果我在Startup.cs的ConfigureServices中添加IIS默认authenticationScheme

services.AddAuthentication(IISDefaults.AuthenticationScheme);

它给了我一个HTTP 403页面:该网站拒绝显示此页面

所以这是最终的代码:

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));
    });

}

如果我理解错误,请纠正我。 谢谢。

选项1:Windows身份验证

您可以为Intranet应用程序打开Windows身份验证。 此处阅读文档。 您可以检查用户是否是一个角色/组通过执行类似这样

在执行此操作之前,可以通过在命令提示符下执行gpresult /R来检查计算机加入的组信息。 有关更多信息,请参见此帖子

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

如果不需要获取与Windows相关的任何信息,则无需将当前主体转换为Windows主体。

如果要获取所有组的列表,则仍然需要查询广告。

警告:

有时与选项2方法相比,在计算机上使用gpresult /R ,我看到某些组未显示在结果中。 这就是为什么有时当您执行User.IsInRole()并且它返回false的原因。 我仍然不知道为什么会这样。

选项2:使用AD查找的表单身份验证

Windows身份验证仅提供有关用户和AD组的一些信息。 有时就足够了,但大多数时候还不够。

您还可以使用常规的表单身份验证并在下面与AD对话并发出Cookie。 这样,尽管用户需要使用其Windows凭据和密码登录到您的应用程序,但是您可以完全控制AD信息。

您不想手工编写所有内容。 幸运的是,有一个Novell.Directory.Ldap.NETStandard可以提供帮助。 您可以在NuGet中找到它。

用于定义您需要从AD中获得什么以及登录协议的接口:

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);
    }
}

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; }
    }
}

用于映射来自appsettings.json的值的ldap配置对象:

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; }
    }
}

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;
        }
    }
}

appsettings.json中的配置(仅作为示例):

{
    "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"
    }
}

设置应用的身份验证(也可能是授权):

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(...);
        }  
    }
}

如何使用身份验证服务对用户进行身份验证:

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);
        }
    }
}

如何读取索赔中存储的信息:

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);
    }
}

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;
        }
    }
}

如何使用策略授权:

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

奖金

您可以从Microsoft下载AD Explorer,以便可视化公司AD。

哎呀。 我原本打算只提供一些技巧,但最终我写了一篇很长的文章。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM