【问题标题】:Using Active Directory and Windows Authentication to give custom roles in Blazor Server使用 Active Directory 和 Windows 身份验证在 Blazor Server 中提供自定义角色
【发布时间】:2020-03-27 10:56:21
【问题描述】:

我正在尝试在我的 Blazor Server 应用程序中提供自定义角色。使用 Windows 身份验证进行身份验证的用户应根据其 Active Directory 组获得这些自定义角色之一,一个组代表一个角色。

如果用户在正确的组中,则用户将获得 RoleClaimType 类型的声明。这些声明稍后用于授权某些页面和操作。

我还没有看到有人这么多谈论使用 Blazor Server 的 Windows 身份验证和 Active Directory,因此我有这些问题。这是我的尝试,但它是来自这里和那里的部分的混合。所以我不确定这是否是最好的方法还是不安全。

这就是我到目前为止所想出的......

ClaimTransformer.cs,我从 appsettings.json 获得了广告组。

public class ClaimsTransformer : IClaimsTransformation
{
    private readonly IConfiguration _configuration;

    public ClaimsTransformer(IConfiguration configuration)
    {
        _configuration = configuration;
    }
    public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        var claimsIdentity = (ClaimsIdentity)principal.Identity
        string adGroup = _configuration.GetSection("Roles")
                    .GetSection("CustomRole")
                    .GetSection("AdGroup").Value;
        
        if (principal.IsInRole(adGroup))
        {
            Claim customRoleClaim = new Claim(claimsIdentity.RoleClaimType, "CustomRole");
            claimsIdentity.AddClaim(customRoleClaim);
        }

        return Task.FromResult(principal);
    }
}

要让 Claimstransformer 与 Authorize 属性一起使用,请在 Startup.cs 中使用:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
   ...

   app.UseAuthorization();
   app.UseAuthentication();

   ...
}
 

我还在 Startup.cs 中注册了 ClaimsTransformer: services.AddScoped&lt;IClaimsTransformation, ClaimsTransformer&gt;();

授权整个 Blazor 组件:

    @attribute [Authorize(Roles = "CustomRole")]

或授权组件的某些部分:

    <AuthorizeView Roles="CustomRole">
        <Authorized>You are authorized</Authorized>
    </AuthorizeView>

所以我的问题基本上是:

- 是否必须重新应用这些声明?如果它们到期,它们什么时候到期 过期了吗?

- 此类授权的最佳做法是什么?

- 这种方式安全吗?

【问题讨论】:

    标签: c# asp.net-core authorization blazor windows-authentication


    【解决方案1】:

    你的问题有点老了,我假设你已经找到了一个解决方案,无论如何,也许还有其他希望在 Windows 身份验证中实现客户角色,所以我发现的简单方法是这样的:

    然后你可以在服务或组件中注入AuthenticationStateProvider

        var authState = await authenticationStateProvider.GetAuthenticationStateAsync();
        var user = authState.User;
        var userClaims = new ClaimsIdentity(new List<Claim>()
            {
                new Claim(ClaimTypes.Role,"Admin")
            });
        user.AddIdentity(userClaims);
    

    这样你就可以设置新的角色了。

    当然,您可以实现自定义逻辑来为每个用户动态添加角色。

    这就是我最终根据 AD 组添加角色的方式:

    public async void GetUserAD()
            {
            var auth = await authenticationStateProvider.GetAuthenticationStateAsync();
            var user = (System.Security.Principal.WindowsPrincipal)auth.User;
    
            using PrincipalContext pc = new PrincipalContext(ContextType.Domain);
            UserPrincipal up = UserPrincipal.FindByIdentity(pc, user.Identity.Name);
    
            FirstName = up.GivenName;
            LastName = up.Surname;
            UserEmail = up.EmailAddress;
            LastLogon = up.LastLogon;
            FixPhone = up.VoiceTelephoneNumber;
            UserDisplayName = up.DisplayName;
            JobTitle = up.Description;
            DirectoryEntry directoryEntry = up.GetUnderlyingObject() as DirectoryEntry;
            Department = directoryEntry.Properties["department"]?.Value as string;
            MobilePhone = directoryEntry.Properties["mobile"]?.Value as string;
            MemberOf = directoryEntry.Properties["memberof"]?.OfType<string>()?.ToList();
    
            if(MemberOf.Any(x=>x.Contains("management-team") && x.Contains("OU=Distribution-Groups")))
            {
                var userClaims = new ClaimsIdentity(new List<Claim>()
                {
                    new Claim(ClaimTypes.Role,"Big-Boss")
                });
                user.AddIdentity(userClaims);
            }
        }
    

    编辑

    您可以在下面找到我如何加载用户信息和分配角色的示例

    using Microsoft.AspNetCore.Components.Authorization;
    using Microsoft.EntityFrameworkCore;
    using System.DirectoryServices;
    using System.DirectoryServices.AccountManagement;
    using System.Linq;
    using System.Security.Claims;
    using System.Threading.Tasks;
    
    public class UserService : IUserService
        {
            private readonly AuthenticationStateProvider authenticationStateProvider;
            private readonly ApplicationDbContext context;
    
            public ApplicationUser CurrentUser { get; private set; }
    
            public UserService(AuthenticationStateProvider authenticationStateProvider, ApplicationDbContext context)
            {
                this.authenticationStateProvider = authenticationStateProvider;
                this.context = context;
            }
    
            public async Task LoadCurrentUserInfoAsync()
            {
                var authState = await authenticationStateProvider.GetAuthenticationStateAsync();
    
    
                using PrincipalContext principalContext = new PrincipalContext(ContextType.Domain);
                UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(principalContext, authState.User.Identity.Name);
                DirectoryEntry directoryEntry = userPrincipal.GetUnderlyingObject() as DirectoryEntry;
    
                CurrentUser.UserName = userPrincipal.SamAccountName;
                CurrentUser.FirstName = userPrincipal.GivenName;
                CurrentUser.LastName = userPrincipal.Surname;
                CurrentUser.Email = userPrincipal.EmailAddress;
                CurrentUser.FixPhone = userPrincipal.VoiceTelephoneNumber;
                CurrentUser.DisplayName = userPrincipal.DisplayName;
                CurrentUser.JobTitle = userPrincipal.Description;
                CurrentUser.Department = directoryEntry.Properties["department"]?.Value as string;
                CurrentUser.MobilePhone = directoryEntry.Properties["mobile"]?.Value as string;
    
                //get user roles from Database
                var roles = context.UserRole
                           .Include(a => a.User)
                           .Include(a => a.Role)
                           .Where(a => a.User.UserName == CurrentUser.UserName)
                           .Select(a => a.Role.Name.ToLower())
                           .ToList();
    
                var claimsIdentity = authState.User.Identity as ClaimsIdentity;
    
                //add custom roles from DataBase
                foreach (var role in roles)
                {
                    var claim = new Claim(claimsIdentity.RoleClaimType, role);
                    claimsIdentity.AddClaim(claim);
                }
    
                //add other types of claims
                var claimFullName = new Claim("fullname", CurrentUser.DisplayName);
                var claimEmail = new Claim("email", CurrentUser.Email);
                claimsIdentity.AddClaim(claimFullName);
                claimsIdentity.AddClaim(claimEmail);
            }
        }
    

    【讨论】:

    • 嗨!是的,您的解决方案与我在问题中所做的一样,向用户提出新的要求。然而,问题是这是否是一种向用户授予角色的安全方式,以及在授权首选 Active Directory 时是否可以使用这种方式。就我而言,没有 Azure。
    • 如何使用此代码?我将此作为服务添加到 DI,当我在 Startup.Configure() 结束时调用它时出现错误:GetAuthenticationStateAsync was called before SetAuthenticationState.
    • @JMooney 我有一个简单的类UserInfo,我在ctor 中注入了AuthenticationStateProvider。我还在启动ConfigureServices 中注册UserInfoScoped
    • 我公司的 AD 设置有点扭曲,我决定将应用程序启动策略添加到用户中,而不是创建角色。对于角色,我遇到了一些错误,我无法找到解决它们的方法。
    • 我很难遵循这个答案,但我认为它会解决我遇到的问题。您能提供更多代码或工作示例吗?
    【解决方案2】:

    我采用了与您类似的方法,但我在范围服务中创建了一个私有 ClaimsPrincipal 对象来存储添加的策略,因为我发现每次调用 TransformAsync 后更改都会丢失。然后我添加了一个简单的 UserInfo 类来获取经过身份验证的用户所属的所有组。

    是否必须重新申请这些声明?如果它们过期,它们什么时候过期?

    据我所知,每次调用 AuthenticateAsync 时都必须重新应用声明。我不确定它们是否会过期,但我认为 Blazor Server 可能会在向客户端发送新差异之前运行 TransformAsync,因此它永远不会被注意到。

    此类授权的最佳做法是什么?

    不知道,但只要您使用 Blazor Server,内置的身份验证和授权中间件可能是最好的方法之一。 WASM 将是一个不同的故事......

    这种方式安全吗?

    我认为安全问题最终会更多地集中在 Web 服务器上,而不是您分配角色的方式上。总的来说它应该是相对安全的,我认为最大的安全问题将取决于诸如

    • 当用户从提供访问权限的组中删除时,应用程序应该立即撤消权限还是可以在下次登录时反映出来。
    • 将用户添加到会在无意中为他们提供访问权限的组有多容易
    • 如果权限基于 OU 等其他用户属性,如果目录发生更改,用户可能会错误地获得或失去访问权限。

    用户授权服务:

    public class UserAuthorizationService : IClaimsTransformation {
    
        public UserInfo userInfo;
    
        private ClaimsPrincipal CustomClaimsPrincipal;
    
        public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) {
            //Creates UserInfo Object on the first Call Only
            if (userInfo == null)
                userInfo = new UserInfo((principal.Identity as WindowsIdentity).Owner.Value); //Owner.Value Stores SID On Smart Card
    
            //Establishes CustomClaimsPrincipal on first Call
            if (CustomClaimsPrincipal == null) {
                CustomClaimsPrincipal = principal;
                var claimsIdentity = new ClaimsIdentity();
    
                //Loop through AD Group list and applies policies
                foreach (var group in userInfo.ADGroups) {
                    switch (group) {
                        case "Example AD Group Name":
                            claimsIdentity.AddClaim(new Claim("ExampleClaim", "Test"));
                            break;
                    }
                }
                CustomClaimsPrincipal.AddIdentity(claimsIdentity);
            }
    
            return Task.FromResult(CustomClaimsPrincipal);
        }
    }
    

    用户信息:

    public class UserInfo {
    
        private DirectoryEntry User { get; set; }
        public List<string> ADGroups { get; set; }
    
        public UserInfo(string SID) {
            ADGroups = new List<string>();
            //Retrieve Current User with SID pulled from Smart Card
            using (DirectorySearcher comps = new DirectorySearcher(new DirectoryEntry("LDAP String For AD"))) {
                comps.Filter = "(&(objectClass=user)(objectSID=" + SID + "))";
                User = comps.FindOne().GetDirectoryEntry();
            }
            //Load List with AD Group Names
            foreach (object group in User.Properties["memberOf"])
                ADGroups.Add(group.ToString()[3..].Split(",OU=")[0]);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-19
      • 2016-10-19
      • 1970-01-01
      • 2016-02-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多