如何在 ASP.NET Identity 中使用和 ASP.NET Membership 数据库? [英] How to consume and ASP.NET Membership database in ASP.NET Identity?

查看:24
本文介绍了如何在 ASP.NET Identity 中使用和 ASP.NET Membership 数据库?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有几个旧的 ASP.NET Web 应用程序共享一个用于 ASP.NET 成员资格的数据库.我想迁移到使用 .NET Core 和 IdentityServer4 的微服务架构,并让新微服务生态系统中的身份服务器使用现有的 ASP.NET Membership 用户存储,但 .NET Core 似乎不支持 ASP.NET Membership全部.

I have a couple of legacy ASP.NET web apps that share a database for ASP.NET Membership. I want to move to a microservices architecture utilizing .NET Core and IdentityServer4 and have the identity server in the new microservices ecosystem to use the existing ASP.NET Membership user store, but .NET Core doesn't appear to support ASP.NET Membership at all.

我目前有一个概念证明,其中涉及一个 Web API、身份服务器和一个 MVC Web 应用程序作为我的客户端.身份服务器实现了 IdentityUser 的一个子类,并实现了 IUserStore/IUserPasswordStore/IUserEmailStore 以使其适应我现有数据库中的 ASP.NET Membership 表.我可以注册新用户并通过我的 POC MVC 客户端应用程序登录,但这些用户无法登录我的旧应用程序.相反,在旧版应用程序中注册的用户无法登录我的 POC MVC 客户端.我认为这是因为我的 IPasswordHasher 实现没有像我的旧应用程序中的 ASP.NET Membership 那样散列密码.

I currently have a proof of concept stood up involving a web API, identity server and an MVC web app as my client. The identity server implements a subclass of IdentityUser and implements IUserStore/IUserPasswordStore/IUserEmailStore to adapt it to the ASP.NET Membership tables in my existing database. I can register new users and login via my POC MVC client app but these users cannot log into my legacy apps. Conversely, users registered in legacy apps can't log into my POC MVC client. I assume its because my implementation of IPasswordHasher isn't hashing the passwords the same as ASP.NET Membership in my legacy apps.

下面是我的代码.任何对我可能做错的事情的见解将不胜感激.安全和密码学不是我的强项.

Below is my code. Any insight into what I might be doing wrong would be greatly appreciated. Security and cryptography are not my strong suit.

Startup.cs

public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

        if (env.IsDevelopment())
        {
            // For more details on using the user secret store see https://go.microsoft.com/fwlink/?LinkID=532709
            builder.AddUserSecrets<Startup>();
        }

        builder.AddEnvironmentVariables();
        Configuration = builder.Build();
    }

    public IConfigurationRoot Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        /* Add CORS policy */
        services.AddCors(options =>
        {
            // this defines a CORS policy called "default"
            options.AddPolicy("default", policy =>
            {
                policy.WithOrigins("http://localhost:5003")
                    .AllowAnyHeader()
                    .AllowAnyMethod();
            });
        });
        services.AddMvcCore()
            .AddAuthorization()
            .AddJsonFormatters();

        /* Add MVC componenets. */
        services.AddMvc();

        /* Configure IdentityServer. */
        services.Configure<IdentityOptions>(options =>
        {
            // Password settings
            options.Password.RequireDigit = true;
            options.Password.RequiredLength = 8;
            options.Password.RequireNonAlphanumeric = false;
            options.Password.RequireUppercase = true;
            options.Password.RequireLowercase = false;

            // Lockout settings
            options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
            options.Lockout.MaxFailedAccessAttempts = 10;

            // Cookie settings
            options.Cookies.ApplicationCookie.ExpireTimeSpan = TimeSpan.FromDays(150);
            options.Cookies.ApplicationCookie.LoginPath = "/Account/Login";
            options.Cookies.ApplicationCookie.LogoutPath = "/Account/Logout";

            // User settings
            options.User.RequireUniqueEmail = true;
        });

        /* Add the DbContext */
        services.AddDbContext<StoreContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("MyConnectionString")));

        /* Add ASP.NET Identity to use for registration and authentication. */
        services.AddIdentity<AspNetMembershipUser, IdentityRole>()
            .AddEntityFrameworkStores<StoreContext>()
            .AddUserStore<AspNetMembershipUserStore>()
            .AddDefaultTokenProviders();

        services.AddTransient<IPasswordHasher<AspNetMembershipUser>, AspNetMembershipPasswordHasher>();

        /* Add IdentityServer and its components. */
        services.AddIdentityServer()
            .AddInMemoryCaching()
            .AddTemporarySigningCredential()
            .AddInMemoryApiResources(Config.GetApiResources())
            .AddInMemoryIdentityResources(Config.GetIdentityResources())
            .AddInMemoryClients(Config.GetClients());
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        /* Configure logging. */
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));

        if (env.IsDevelopment())
        {
            loggerFactory.AddDebug();
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
            app.UseBrowserLink();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

        /* Configure wwwroot */
        app.UseStaticFiles();

        /* Configure CORS */
        app.UseCors("default");

        /* Configure AspNet Identity */
        app.UseIdentity();

        /* Configure IdentityServer */
        app.UseIdentityServer();

        /* Configure MVC */
        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

AspNetMembershipUser.cs

public class AspNetMembershipUser : IdentityUser
{
    public string PasswordSalt { get; set; }
    public int PasswordFormat { get; set; }
}

AspNetMembershipUserStore.cs

public class AspNetMembershipUserStore : IUserStore<AspNetMembershipUser>, IUserPasswordStore<AspNetMembershipUser>, IUserEmailStore<AspNetMembershipUser>
{
    private readonly StoreContext _dbcontext;

    public AspNetMembershipUserStore(StoreContext dbContext)
    {
        _dbcontext = dbContext;
    }

    public Task<IdentityResult> CreateAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() =>
        {
            try
            {
                User dbUser = new User();
                this.Convert(user, dbUser);
                _dbcontext.Users.Add(dbUser);
                _dbcontext.SaveChanges();
                return IdentityResult.Success;
            }
            catch (Exception ex)
            {
                return IdentityResult.Failed(new IdentityError
                {
                    Code = ex.GetType().Name,
                    Description = ex.Message
                });
            }
        });
    }

    public Task<IdentityResult> DeleteAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() =>
        {
            try
            {
                User dbUser = _dbcontext.Users
                    .Include(u => u.AspNetUsers).ThenInclude(u => u.AspNetMembership)
                    .Include(u => u.AspNetUsers).ThenInclude(u => u.AspNetApplication)
                    .Include(u => u.UserGroups)
                    .SingleOrDefault(u => u.ProviderUserName == user.NormalizedUserName);

                if (dbUser != null)
                {
                    _dbcontext.AspNetUsers.Remove(dbUser.AspNetUser);
                    _dbcontext.Users.Remove(dbUser);
                    _dbcontext.SaveChanges();
                }

                return IdentityResult.Success;
            }
            catch (Exception ex)
            {
                return IdentityResult.Failed(new IdentityError
                {
                    Code = ex.GetType().Name,
                    Description = ex.Message
                });
            }
        });
    }

    public void Dispose()
    {
        _dbcontext.Dispose();
    }

    public Task<AspNetMembershipUser> FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() =>
        {
            User dbUser = _dbcontext.Users
                .Include(u => u.AspNetUsers).ThenInclude(u => u.AspNetMembership)
                .Include(u => u.AspNetUsers).ThenInclude(u => u.AspNetApplication)
                .Include(u => u.UserGroups)
                .SingleOrDefault(u => u.ProviderEmailAddress == normalizedEmail);

            if (dbUser == null)
            {
                return null;
            }

            AspNetMembershipUser user = new AspNetMembershipUser();
            this.Convert(dbUser, user);
            return user;
        });
    }

    public Task<AspNetMembershipUser> FindByIdAsync(string userId, CancellationToken cancellationToken)
    {
        long lUserId = long.Parse(userId);
        return Task.Factory.StartNew(() =>
        {
            User dbUser = _dbcontext.Users
                .Include(u => u.AspNetUsers).ThenInclude(u => u.AspNetMembership)
                .Include(u => u.AspNetUsers).ThenInclude(u=> u.AspNetApplication)
                .Include(u => u.UserGroups)
                .SingleOrDefault(u => u.UserId == lUserId);

            if (dbUser == null)
            {
                return null;
            }

            AspNetMembershipUser user = new AspNetMembershipUser();
            this.Convert(dbUser, user);
            return user;
        });
    }

    public Task<AspNetMembershipUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() =>
        {
            User dbUser = _dbcontext.Users
                .Include(u => u.AspNetUsers).ThenInclude(u => u.AspNetMembership)
                .Include(u => u.AspNetUsers).ThenInclude(u => u.AspNetApplication)
                .Include(u => u.UserGroups)
                .SingleOrDefault(u => u.ProviderUserName == normalizedUserName);

            if (dbUser == null)
            {
                return null;
            }

            AspNetMembershipUser user = new AspNetMembershipUser();
            this.Convert(dbUser, user);
            return user;
        });
    }

    public Task<string> GetEmailAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() => user.Email);
    }

    public Task<bool> GetEmailConfirmedAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() => user.EmailConfirmed);
    }

    public Task<string> GetNormalizedEmailAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() => user.NormalizedEmail);
    }

    public Task<string> GetNormalizedUserNameAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() => user.NormalizedUserName);
    }

    public Task<string> GetPasswordHashAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() => user.PasswordHash);
    }

    public Task<string> GetUserIdAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() => user.Id.ToString());
    }

    public Task<string> GetUserNameAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() => user.UserName);
    }

    public Task<bool> HasPasswordAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() => !string.IsNullOrEmpty(user.PasswordHash));
    }

    public Task SetEmailAsync(AspNetMembershipUser user, string email, CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() => user.Email = email);
    }

    public Task SetEmailConfirmedAsync(AspNetMembershipUser user, bool confirmed, CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() => user.EmailConfirmed = confirmed);
    }

    public Task SetNormalizedEmailAsync(AspNetMembershipUser user, string normalizedEmail, CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() => user.NormalizedEmail = normalizedEmail);
    }

    public Task SetNormalizedUserNameAsync(AspNetMembershipUser user, string normalizedName, CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() => user.NormalizedUserName = normalizedName);
    }

    public Task SetPasswordHashAsync(AspNetMembershipUser user, string passwordHash, CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() => user.PasswordHash = passwordHash);
    }

    public Task SetUserNameAsync(AspNetMembershipUser user, string userName, CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() => user.UserName = userName);
    }

    public Task<IdentityResult> UpdateAsync(AspNetMembershipUser user, CancellationToken cancellationToken)
    {
        return Task.Factory.StartNew(() =>
        {
            try
            {
                User dbUser = _dbcontext.Users
                    .Include(u => u.AspNetUsers).ThenInclude(u => u.AspNetMembership)
                    .Include(u => u.AspNetUsers).ThenInclude(u => u.AspNetApplication)
                    .Include(u => u.UserGroups)
                    .SingleOrDefault(u => u.UserId.ToString() == user.Id);

                if (dbUser != null)
                {
                    this.Convert(user, dbUser);
                    _dbcontext.Users.Update(dbUser);
                    _dbcontext.SaveChanges();
                }
                return IdentityResult.Success;
            }
            catch(Exception ex)
            {
                return IdentityResult.Failed(new IdentityError
                {
                    Code = ex.GetType().Name,
                    Description = ex.Message
                });
            }
        });
    }

    private void Convert(User from, AspNetMembershipUser to)
    {
        to.Id = from.ProviderUserKey.ToString();
        to.UserName = from.ProviderUserName;
        to.NormalizedUserName = from.ProviderUserName.ToLower();
        to.Email = from.ProviderEmailAddress;
        to.NormalizedEmail = from.ProviderEmailAddress.ToLower();
        to.EmailConfirmed = true;
        to.PasswordHash = from.AspNetUser.AspNetMembership.Password;
        to.PasswordSalt = from.AspNetUser.AspNetMembership.PasswordSalt;
        to.PasswordFormat = from.AspNetUser.AspNetMembership.PasswordFormat;
        to.AccessFailedCount = from.AspNetUser.AspNetMembership.FailedPasswordAttemptCount;
        to.EmailConfirmed = true;
        to.Roles.Clear();
        from.UserGroups.ToList().ForEach(ug =>
        {
            to.Roles.Add(new IdentityUserRole<string>
            {
                RoleId = ug.GroupId.ToString(),
                UserId = ug.UserId.ToString()
            });
        });
        to.PhoneNumber = from.Phone ?? from.ShippingPhone;
        to.PhoneNumberConfirmed = !string.IsNullOrEmpty(to.PhoneNumber);
        to.SecurityStamp = from.AspNetUser.AspNetMembership.PasswordSalt;
    }

    private void Convert(AspNetMembershipUser from , User to)
    {
        AspNetApplication application = _dbcontext.AspNetApplications.First();

        to.ProviderUserKey = Guid.Parse(from.Id);
        to.ProviderUserName = from.UserName;
        to.ProviderEmailAddress = from.Email;
        to.InternalEmail = $"c_{Guid.NewGuid().ToString()}@mycompany.com";
        to.AccountOwner = "MYCOMPANY";
        to.UserStatusId = (int)UserStatus.Normal;

        AspNetUser aspNetUser = to.AspNetUser;

        if (to.AspNetUser == null)
        {
            to.AspNetUser = new AspNetUser
            {
                ApplicationId = application.ApplicationId,
                AspNetApplication= application,
                AspNetMembership = new AspNetMembership
                {
                    ApplicationId = application.ApplicationId,
                    AspNetApplication = application
                }
            };
        }

        to.AspNetUser.UserId = Guid.Parse(from.Id);
        to.AspNetUser.UserName = from.UserName;
        to.AspNetUser.LoweredUserName = from.UserName.ToLower();
        to.AspNetUser.LastActivityDate = DateTime.UtcNow;
        to.AspNetUser.IsAnonymous = false;
        to.AspNetUser.ApplicationId = application.ApplicationId;
        to.AspNetUser.AspNetMembership.CreateDate = DateTime.UtcNow;
        to.AspNetUser.AspNetMembership.Email = from.Email;
        to.AspNetUser.AspNetMembership.IsApproved = true;
        to.AspNetUser.AspNetMembership.LastLoginDate = DateTime.Parse("1754-01-01 00:00:00.000");
        to.AspNetUser.AspNetMembership.LastLockoutDate = DateTime.Parse("1754-01-01 00:00:00.000");
        to.AspNetUser.AspNetMembership.LastPasswordChangedDate = DateTime.Parse("1754-01-01 00:00:00.000");
        to.AspNetUser.AspNetMembership.LoweredEmail = from.NormalizedEmail.ToLower();
        to.AspNetUser.AspNetMembership.Password = from.PasswordHash;
        to.AspNetUser.AspNetMembership.PasswordSalt = from.PasswordSalt;
        to.AspNetUser.AspNetMembership.PasswordFormat = from.PasswordFormat;
        to.AspNetUser.AspNetMembership.IsLockedOut = false;
        to.AspNetUser.AspNetMembership.FailedPasswordAnswerAttemptWindowStart = DateTime.Parse("1754-01-01 00:00:00.000");
        to.AspNetUser.AspNetMembership.FailedPasswordAttemptWindowStart = DateTime.Parse("1754-01-01 00:00:00.000");

        // Merge Groups/Roles
        to.UserGroups
            .Where(ug => !from.Roles.Any(r => ug.GroupId.ToString() == r.RoleId))
            .ToList()
            .ForEach(ug => to.UserGroups.Remove(ug));

        to.UserGroups
            .Join(from.Roles, ug => ug.GroupId.ToString(), r => r.RoleId, (ug, r) => new { To = ug, From = r })
            .ToList()
            .ForEach(j =>
            {
                j.To.UserId = long.Parse(j.From.UserId);
                j.To.GroupId = int.Parse(j.From.RoleId);
            });

        from.Roles
            .Where(r => !to.UserGroups.Any(ug => ug.GroupId.ToString() == r.RoleId))
            .ToList()
            .ForEach(r =>
            {
                to.UserGroups.Add(new UserGroup
                {
                    UserId = long.Parse(from.Id),
                    GroupId = int.Parse(r.RoleId)
                });
            });
    }
}

AspNetMembershipPasswordHasher.cs

public class AspNetMembershipPasswordHasher : IPasswordHasher<AspNetMembershipUser>
{
    private readonly int _saltSize;
    private readonly int _bytesRequired;
    private readonly int _iterations;

    public AspNetMembershipPasswordHasher()
    {
        this._saltSize = 128 / 8;
        this._bytesRequired = 32;
        this._iterations = 1000;
    }

    public string HashPassword(AspNetMembershipUser user, string password)
    {
        string passwordHash = null;
        string passwordSalt = null;

        this.HashPassword(password, out passwordHash, ref passwordSalt);

        user.PasswordSalt = passwordSalt;
        return passwordHash;
    }

    public PasswordVerificationResult VerifyHashedPassword(AspNetMembershipUser user, string hashedPassword, string providedPassword)
    {
        // Throw an error if any of our passwords are null
        if (hashedPassword == null)
        {
            throw new ArgumentNullException("hashedPassword");
        }

        if (providedPassword == null)
        {
            throw new ArgumentNullException("providedPassword");
        }

        string providedPasswordHash = null;

        if (user.PasswordFormat == 0)
        {
            providedPasswordHash = providedPassword;
        }
        else if (user.PasswordFormat == 1)
        {

            string providedPasswordSalt = user.PasswordSalt;

            this.HashPassword(providedPassword, out providedPasswordHash, ref providedPasswordSalt);
        }
        else
        {
            throw new NotSupportedException("Encrypted passwords are not supported.");
        }

        if (providedPasswordHash == hashedPassword)
        {
            return PasswordVerificationResult.Success;
        }
        else
        {
            return PasswordVerificationResult.Failed;
        }
    }

    private void HashPassword(string password, out string passwordHash, ref string passwordSalt)
    {
        byte[] hashBytes = null;
        byte[] saltBytes = null;
        byte[] totalBytes = new byte[this._saltSize + this._bytesRequired];

        if (!string.IsNullOrEmpty(passwordSalt))
        {
            // Using existing salt.
            using (var pbkdf2 = new Rfc2898DeriveBytes(password, Convert.FromBase64String(passwordSalt), this._iterations))
            {
                saltBytes = pbkdf2.Salt;
                hashBytes = pbkdf2.GetBytes(this._bytesRequired);
            }
        }
        else
        {
            // Generate a new salt.
            using (var pbkdf2 = new Rfc2898DeriveBytes(password, this._saltSize, this._iterations))
            {
                saltBytes = pbkdf2.Salt;
                hashBytes = pbkdf2.GetBytes(this._bytesRequired);
            }
        }

        Buffer.BlockCopy(saltBytes, 0, totalBytes, 0, this._saltSize);
        Buffer.BlockCopy(hashBytes, 0, totalBytes, this._saltSize, this._bytesRequired);

        using (SHA256 hashAlgorithm = SHA256.Create())
        {
            passwordHash = Convert.ToBase64String(hashAlgorithm.ComputeHash(totalBytes));
            passwordSalt = Convert.ToBase64String(saltBytes);
        }
    }
}

推荐答案

我的一位同事能够帮助我.下面是哈希函数的样子.通过此更改,ASP.NET Identity 能够搭载现有的 ASP.NET Membership 数据库.

One of my coworkers was able to help me out. Below is what the hash function should look like. With this change, ASP.NET Identity is able to piggy back on an existing ASP.NET Membership database.

private void HashPassword(string password, out string passwordHash, ref string passwordSalt)
    {
        byte[] passwordBytes = Encoding.Unicode.GetBytes(password);
        byte[] saltBytes = null;

        if (!string.IsNullOrEmpty(passwordSalt))
        {
            saltBytes = Convert.FromBase64String(passwordSalt);
        }
        else
        {
            saltBytes = new byte[128 / 8];
            using (var rng = RandomNumberGenerator.Create())
            {
                rng.GetBytes(saltBytes);
            }
        }

        byte[] totalBytes = new byte[saltBytes.Length + passwordBytes.Length];
        Buffer.BlockCopy(saltBytes, 0, totalBytes, 0, saltBytes.Length);
        Buffer.BlockCopy(passwordBytes, 0, totalBytes, saltBytes.Length, passwordBytes.Length);

        using (SHA1 hashAlgorithm = SHA1.Create())
        {
            passwordHash = Convert.ToBase64String(hashAlgorithm.ComputeHash(totalBytes));
        }

        passwordSalt = Convert.ToBase64String(saltBytes);
    }

您可以在 GitHib 上找到所有 源代码.

You can find all the source code on GitHib.

这篇关于如何在 ASP.NET Identity 中使用和 ASP.NET Membership 数据库?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆