使用自定义表和 Entity Framework 核心的身份核心角色授权

Identity Core role authorization with custom Tables and Entity Framework Core

我快到了,我使用 Core Identity 实现了我的自定义身份验证,因为我已经有一些 table 与用户、角色和一个加入 table 与角色分配给用户。

不要介意错误的模型 属性 名称,遗憾的是我不得不处理一个设计非常糟糕的数据库,其中没有保存加密的密码(我做了一个解决方法,你可以在下面的代码中看到,为了跳过密码散列并且它有效)。

我也从一个非常简单的角色硬编码测试开始,但我无法访问该代码。 我将我的 DbContext 称为“OracleContext”,因此我的模型是从现有数据库构建的。

我将 .Net Core 3.1 与 Blazor Server 一起使用,我创建了我的 UserStore 对象,实现了最少数量的接口: UserStore.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

namespace core_identity_utenza_bms.Data
{
    public class UserStore : IUserStore<Geutenti>, IUserPasswordStore<Geutenti>, IUserRoleStore<Geutenti>
    {
        private readonly OracleContext _oracleContext;

        public UserStore(OracleContext oracleContext)
        {
            _oracleContext = oracleContext;
        }
        public void Dispose()
        {
            //throw new NotImplementedException();
        }

        public async Task<string> GetUserIdAsync(Geutenti user, CancellationToken cancellationToken)
        {
            return user.Cuser.ToString();
        }

        public async Task<string> GetUserNameAsync(Geutenti user, CancellationToken cancellationToken)
        {
            return user.Ruser;
        }

        public async Task SetUserNameAsync(Geutenti user, string userName, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        public async Task<string> GetNormalizedUserNameAsync(Geutenti user, CancellationToken cancellationToken)
        {
            return user.Tuser;
        }

        public async Task SetNormalizedUserNameAsync(Geutenti user, string normalizedName, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        public async Task<IdentityResult> CreateAsync(Geutenti user, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        public async Task<IdentityResult> UpdateAsync(Geutenti user, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        public async Task<IdentityResult> DeleteAsync(Geutenti user, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        public async Task<Geutenti> FindByIdAsync(string userId, CancellationToken cancellationToken)
        {
            var userQuery =
                await _oracleContext.Geutenti.Where(
                    u =>
                        u.Cuser.ToString().Equals(userId) &&
                        u.Bstor.Equals("A") &&
                        u.Csoci.Equals("MASM")).FirstOrDefaultAsync(cancellationToken);
            return userQuery;
        }

        public async Task<Geutenti> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
        {
            var userQuery =
                await _oracleContext.Geutenti.Where(
                        u =>
                            u.Ruser.Contains("ARDILLOI") &&
                            u.Bstor.Equals("A") &&
                            u.Csoci.Equals("MASM"))
                    .FirstOrDefaultAsync(cancellationToken);
            return userQuery;
        }

        public async Task SetPasswordHashAsync(Geutenti user, string passwordHash, CancellationToken cancellationToken)
        {
            //throw new NotImplementedException();
        }

        public async Task<string> GetPasswordHashAsync(Geutenti user, CancellationToken cancellationToken)
        {
            return user.CpaswDuser;
        }

        public async Task<bool> HasPasswordAsync(Geutenti user, CancellationToken cancellationToken)
        {
            return !(user.CpaswDuser == null || user.CpaswDuser.Equals(string.Empty));
        }

        /*
         * UserRole
         */
        public async Task AddToRoleAsync(Geutenti user, string roleName, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        public async Task RemoveFromRoleAsync(Geutenti user, string roleName, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        public async Task<IList<string>> GetRolesAsync(Geutenti user, CancellationToken cancellationToken)
        {
            //var userRoles = _oracleContext.Geruxute.
            return new List<string> {"BURBERO", "BARBARO"};
        }

        public async Task<bool> IsInRoleAsync(Geutenti user, string roleName, CancellationToken cancellationToken)
        {
            return new List<string> { "BURBERO", "BARBARO" }.Contains(roleName);

        }

        public async Task<IList<Geutenti>> GetUsersInRoleAsync(string roleName, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }
    }
}

然后我实现了一个 class 来跳过密码散列: NoPasswordHasher.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;

namespace core_identity_utenza_bms.Data
{
    public class NoPasswordHasher : IPasswordHasher<Geutenti>
    {
        public string HashPassword(Geutenti user, string password)
        {
            return password;
        }

        public PasswordVerificationResult VerifyHashedPassword(Geutenti user, string hashedPassword, string providedPassword)
        {
            return hashedPassword.Equals(providedPassword) ? PasswordVerificationResult.Success : PasswordVerificationResult.Failed;
        }
    }
}

我创建了一个 RoleStore.cs 文件来检索我在 db:

上的角色
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

namespace core_identity_utenza_bms.Data
{
    public class RoleStore : IRoleStore<Geruoliz>
    {
        private readonly OracleContext _oracleContext;

        public RoleStore(OracleContext oracleContext)
        {
            _oracleContext = oracleContext;
        }
        public void Dispose()
        {
            //throw new NotImplementedException();
        }

        public async Task<IdentityResult> CreateAsync(Geruoliz role, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        public async Task<IdentityResult> UpdateAsync(Geruoliz role, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        public async Task<IdentityResult> DeleteAsync(Geruoliz role, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        public async Task<string> GetRoleIdAsync(Geruoliz role, CancellationToken cancellationToken)
        {
            return role.Crule.ToString();
        }

        public async Task<string> GetRoleNameAsync(Geruoliz role, CancellationToken cancellationToken)
        {
            return role.Rrule;
        }

        public async Task SetRoleNameAsync(Geruoliz role, string roleName, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        public async Task<string> GetNormalizedRoleNameAsync(Geruoliz role, CancellationToken cancellationToken)
        {
            return role.Rrule;
        }

        public async Task SetNormalizedRoleNameAsync(Geruoliz role, string normalizedName, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        public async Task<Geruoliz> FindByIdAsync(string roleId, CancellationToken cancellationToken)
        {
            var role = await _oracleContext.Geruoliz.Where(
                    r =>
                        r.Crule.ToString().Equals(roleId) &&
                        r.Bstor.Equals("A") &&
                        r.Csoci.Equals("MASM"))
                .FirstOrDefaultAsync(cancellationToken: cancellationToken);
            return role;
        }

        public async Task<Geruoliz> FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken)
        {
            var role = await _oracleContext.Geruoliz.Where(
                    r =>
                        r.Rrule.Equals(normalizedRoleName) &&
                        r.Bstor.Equals("A") &&
                        r.Csoci.Equals("MASM"))
                .FirstOrDefaultAsync(cancellationToken: cancellationToken);
            return role;
        }
    }
}

最后我编辑了 Startup.cs:

services.AddDbContext<OracleContext>();
services
    .AddDefaultIdentity<Geutenti>()
    .AddUserStore<UserStore>();
services.AddScoped<IPasswordHasher<Geutenti>, NoPasswordHasher>();

现在我什至无法在 UserStore.GetRolesAsync 和 UserStore.IsInRoleAsync 中输入断点,因为我想我应该通过这些检查以确定视图是否可见,因为例如:

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
<AuthorizeView>
    <p>sei loggato.</p>
</AuthorizeView>
<AuthorizeView Roles="BURBERO">
    <Authorized>
        Sei un burbero!
    </Authorized>
    <NotAuthorized>
        Sei una brava persona.
    </NotAuthorized>
</AuthorizeView>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

我只在未指定任何角色的授权视图中成功。 我尝试的最后一件事是编辑服务,例如:

services.AddDbContext<OracleContext>();
services
    .AddDefaultIdentity<Geutenti>()
    .AddUserStore<UserStore>()
    .AddRoleStore<RoleStore>();
services.AddScoped<IPasswordHasher<Geutenti>, NoPasswordHasher>();

但它给了我例外:

System.InvalidOperationException: 'No RoleType was specified, try AddRoles<TRole>().'

到现在为止,我真的对网络上分散的信息感到不知所措。 有什么帮助吗?非常感谢您的宝贵时间!

我找到了解决办法。这既是与缓存相关的问题,也是与启动设置有关的问题。 上面的所有代码都有效,我必须在 Startup 中设置:

// Add identity types
services.AddDbContext<OracleContext>();
services
    .AddIdentity<Geutenti, Geruoliz>()
    .AddRoles<Geruoliz>()
    .AddDefaultTokenProviders();
// Identity Services
services.AddTransient<IUserStore<Geutenti>, UserStore>();
services.AddTransient<IRoleStore<Geruoliz>, RoleStore>();
services.AddScoped<IPasswordHasher<Geutenti>, NoPasswordHasher>();

这里的对象是:

  • OracleContext:它是来自 Entity Framework Core 的数据库上下文对象,它包装了我的数据库,其中包含我的三个用户 tables
  • Geutenti:用户 table,以及所有相关数据(用户名、密码、电子邮件等)
  • Geruoliz:角色 table
  • UserStore 和 RoleStore:实现那些通知 Core Identity 如何访问用户相关数据以进行身份​​验证和授权的接口的对象
  • NoPasswordHasher:跳过密码散列的解决方法。 (声明:这引入了一个安全漏洞,你永远不应该按原样保存密码,而是保存它们的哈希值)

我必须 logout/erase cookie 并重新启动项目,登录等!