ASP.Net Core 2.1 注册自定义 ClaimsPrincipal

Posted

技术标签:

【中文标题】ASP.Net Core 2.1 注册自定义 ClaimsPrincipal【英文标题】:ASP.Net Core 2.1 register custom ClaimsPrincipal 【发布时间】:2019-01-24 02:39:49 【问题描述】:

我正在创建一个 Windows 身份验证应用程序,但角色位于自定义数据库中而不是 AD 上,因此我创建了一个自定义 ClaimsPrincipal 来覆盖通常在 AD 中查看角色的 User.IsInRole() 函数。

但是,在运行应用程序时,它似乎仍在使用原始代码,而不是我的 CustomClaimsPrincipal。 我收到错误“主域和受信任域之间的信任关系失败”。

在 ASP.Net MVC 5 中,我使用了一个自定义 RoleProvider,这基本上就是我在这里尝试复制的内容。

CustomClaimsPrincipal.cs

public class CustomClaimsPrincipal : ClaimsPrincipal

    private readonly ApplicationDbContext _context;

    public CustomClaimsPrincipal(ApplicationDbContext context)
    
        _context = context;
    

    public override bool IsInRole(string role)
    
        var currentUser = ClaimsPrincipal.Current.Identity.Name;

        IdentityUser user = _context.Users.FirstOrDefault(u => u.UserName.Equals(currentUser, StringComparison.CurrentCultureIgnoreCase));

        var roles = from ur in _context.UserRoles.Where(p => p.UserId == user.Id)
                    from r in _context.Roles
                    where ur.RoleId == r.Id
                    select r.Name;
        if (user != null)
            return roles.Any(r => r.Equals(role, StringComparison.CurrentCultureIgnoreCase));
        else
            return false;
    

Startup.cs

        services.AddIdentity<ApplicationUser, IdentityRole>().AddEntityFrameworkStores<ApplicationDbContext>();

        services.AddScoped<ClaimsPrincipal,CustomClaimsPrincipal>();

不确定 Startup.cs 中的上述代码是否是覆盖 ClaimsPrincipal 的正确方法,因为我是 .Net Core 框架的新手。

【问题讨论】:

ClaimsPrincipal 没有作为服务注入,所以你正在做的事情不会起作用。它由身份验证提供程序设置在HttpContext.User 没有很好的文档记录,它不会抛出异常,但不支持自定义 ClaimsPrincipal 并且可能会在管道中进一步消失:github.com/aspnet/Security/issues/323 RoleProvider 不是 MVC 框架类,它是 Identity Framework 类。 And it still exists as a Store Provider. 【参考方案1】:

我想我会以不同的方式解决这个问题:我不会尝试让 ClaimsPrincipal 的实例与数据库对话以确定它们是否属于特定角色,而是修改 ClaimsPrincipal 并添加它们的角色属于ClaimsPrincipal 实例。

为此,我会使用一个很遗憾没有很好记录的功能。身份验证管道公开了一个可扩展点,一旦身份验证完成,您就可以转换创建的ClaimsPrincipal 实例。这可以通过IClaimsTransformation 接口完成。

代码可能类似于:

public class Startup

    public void ConfigureServices(ServiceCollection services)
    
        // Here you'd have your registrations

        services.AddTransient<IClaimsTransformation, ClaimsTransformer>();
    


public class ClaimsTransformer : IClaimsTransformation

    private readonly ApplicationDbContext _context;

    public ClaimsTransformer(ApplicationDbContext context)
    
        _context = context;
    

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    
        var existingClaimsIdentity = (ClaimsIdentity)principal.Identity;
        var currentUserName = existingClaimsIdentity.Name;

        // Initialize a new list of claims for the new identity
        var claims = new List<Claim>
        
            new Claim(ClaimTypes.Name, currentUserName),
            // Potentially add more from the existing claims here
        ;

        // Find the user in the DB
        // Add as many role claims as they have roles in the DB
        IdentityUser user = await _context.Users.FirstOrDefaultAsync(u => u.UserName.Equals(currentUserName, StringComparison.CurrentCultureIgnoreCase));
        if (user != null)
        
            var rolesNames = from ur in _context.UserRoles.Where(p => p.UserId == user.Id)
                        from r in _context.Roles
                        where ur.RoleId == r.Id
                        select r.Name;

            claims.AddRange(rolesNames.Select(x => new Claim(ClaimTypes.Role, x)));
        

        // Build and return the new principal
        var newClaimsIdentity = new ClaimsIdentity(claims, existingClaimsIdentity.AuthenticationType);
        return new ClaimsPrincipal(newClaimsIdentity);
    

为了全面披露,TransformAsync 方法将在每次身份验证过程发生时运行,因此很可能在每个请求上,也意味着它将在每个请求上查询数据库以获取登录用户的角色。

与修改ClaimsPrincipal 的实现相比,使用此解决方案的优势在于ClaimsPrincipal 现在是 并且不绑定到您的数据库。只有身份验证管道知道它,这使得测试之类的事情变得更容易,例如,使用特定角色新建一个 ClaimsPrincipal 以确保他们可以或不可以访问特定操作,而不会被绑定到数据库。

【讨论】:

一个警告:你有 currentUser 而不是 currentUserName 在这一行: IdentityUser user = await _context.Users.FirstOrDefaultAsync(u => u.UserName.Equals(currentUser, StringComparison.CurrentCultureIgnoreCase));跨度> 感谢@RyanBattistone 将立即进行编辑! 我会说 - 这是一种快速创建自定义声明的/出色/轻松的方式。这比我在 MSDN 中找到的任何解释都更好——它通常将这些东西“挥手”到一个你无法真正修改的封闭类中。 (也与 EF Core Power Tools 完美搭配,适用于那些希望采用数据优先方法处理已在数据库中构建的声明的人。) 谢谢,这很好用。不过有一次问题:每个请求都会多次调用 TransformAsync 函数。已经尝试将服务添加为范围服务,但这没有帮助。有谁知道它为什么会运行多次,或者如何每次请求只调用一次函数? 它被多次调用的事实与用于服务注册的生命周期无关。您无法阻止它被多次调用,但您可以通过返回新的ClaimsPrincipal 而不是修改后的来防止副作用。更多信息在 Brock Allen 的帖子中:brockallen.com/2017/08/30/…

以上是关于ASP.Net Core 2.1 注册自定义 ClaimsPrincipal的主要内容,如果未能解决你的问题,请参考以下文章

ASP.Net Core 2.1 中的身份< - 自定义 AccountController

带有 Windows 身份验证的 ASP.NET Core 2.1 自定义 RoleProvider

asp.net core2 mvc 基础教程--服务注册和管道

基于 ASP.NET Core 2.1 的 Razor Class Library 实现自定义错误页面的公用类库

有没有办法在 ASP.NET Core 2.1 中捕获对 Razor 页面不存在的自定义处理程序的 HTTP 请求?

Asp.Net Core 2.1 Identity - UserStore 依赖注入