自定義表並實現Identity登錄(一)


注意,Microsoft.AspNet.Identity.Core.1.0.0和Microsoft.AspNet.Identity.Core.2.2.1差別太大,需考慮實際項目中用的是哪種,本文是基於2.2.1來寫的!!

 

兩個概念:1、OWIN的英文全稱是Open Web Interface for .NET。2、Microsoft.AspNet.Identity是微軟在MVC 5.0中新引入的一種membership框架,和之前ASP.NET傳統的membership以及WebPage所帶來的SimpleMembership(在MVC 4中使用)都有所不同。本文以自定義數據庫表來實現用戶的登錄授權。不依賴MemberShip和EF框架。 

要實現自定義,有三處需重寫:UserStore、PasswordHasher、IUser

自定義UserStore:

  繼承微軟的UserStore並重新實現它的FindByIdAsync、FindByNameAsync等方法。

  為什么要這么做?

  1、替換EF並換成我們自己的底層ORM(我在另一篇文章中用EF寫了一套)

  2、多個Area可以自定義多個UserStore,不同的Area可以訪問不同的UserStore,當然多個Area訪問同一個UserStore也是可以。

  它是如何工作的?

    public class UserManager<TUser> : IDisposable where TUser: IUser
    {
        private ClaimsIdentityFactory<TUser> _claimsFactory;
        private bool _disposed;
        private IPasswordHasher _passwordHasher;
        private IIdentityValidator<string> _passwordValidator;
        private IIdentityValidator<TUser> _userValidator;
        
        public UserManager(IUserStore<TUser> store)
        {
            if (store == null)
            {
                throw new ArgumentNullException("store");
            }
            this.Store = store;
            this.UserValidator = new UserValidator<TUser>((UserManager<TUser>) this);
            this.PasswordValidator = new MinimumLengthValidator(6);
            this.PasswordHasher = new Microsoft.AspNet.Identity.PasswordHasher();
            this.ClaimsIdentityFactory = new ClaimsIdentityFactory<TUser>();
        }
UserManager代碼

  通過UserManager的源代碼可以看到,UserManager運用了橋接模式,查找用戶的方法來自UserStore,返回的是泛型

 

自定義IUserIdentity:

  1、需注意,此處可不是繼承IUserIdentity,IUserIdentity需和IdentityDbContext配合,我們既然用自己的ORM,就等於放棄IdentityDbContext。

namespace Biz.Framework.AspNet.Identity
{
    /// <summary>
    /// IdentityLoginUser,重寫Identity.IUser,這個跟數據庫中的用戶表是兩碼事,專門做登錄、權限判斷用的
    /// 但是,我們可以把它跟數據庫的用戶表結合起來用,上面部分為數據的用戶表,下面為繼承成IUserIdentity(可做公司產品)
    /// </summary>
    [Serializable]
    public class IdentityLoginUser : ISerializable, IUserIdentity, Microsoft.AspNet.Identity.IUser
    {
        #region 數據庫字段
        public int UserId { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
        #endregion
        

        #region 公司自己的產品IUserIdentity接口
        bool? IUserIdentity.IsRootAdmin
        {
            get { return null; }
        }
        #endregion


        #region 微軟IUser接口
        string Microsoft.AspNet.Identity.IUser<string>.Id
        {
            get { return null; }
        }
        #endregion


        #region 必須實現,否則無法使用
        [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("UserId", this.UserId);
            info.AddValue("Password", string.Empty);
        }

        /// <summary>
        /// 反序列化的構造函數
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        public IdentityLoginUser(SerializationInfo info, StreamingContext context)
        {
            this.UserId = info.GetInt32("UserId");
            this.Password = info.GetString("Password");
        }
        #endregion

        public string ToJson() { return JsonConvert.SerializeObject(this); }
    }
}
自定義IUserIdentity

  2、微軟自帶的User字段肯定是不符合實際環境的字段

 

自定義UserStore,整體代碼如下

using Biz.DBUtility;//這個是自己的底層ORM,可以是EF的edmx或者其他
using Microsoft.AspNet.Identity;
using System;
using System.Data.Entity;
using System.Threading.Tasks;

namespace Biz.Framework.AspNet.Identity
{
    /// <summary>
    /// 針對不同的Area,可以定義多個UserStore。
    /// TODO:可以支持多種形式的用戶讀取嗎?比如AD,接口驗證等?待研究
    /// </summary>
    public class BizUserStore : IUserPasswordStore<IdentityLoginUser>, IUserStore<IdentityLoginUser>, IDisposable
    {
        public Task CreateAsync(IdentityLoginUser user)
        {
            throw new NotImplementedException("不支持新增用戶");
        }

        public Task DeleteAsync(IdentityLoginUser user)
        {
            throw new NotImplementedException("不支持刪除用戶");
        }

        public Task<IdentityLoginUser> FindByIdAsync(int userId)
        {
            //BizEntities b = new BizEntities();
            return null;//b.Sys_User.FindAsync(new object[] { userId });
        }

        public Task<IdentityLoginUser> FindByNameAsync(string userName)
        {
            if (string.IsNullOrEmpty(userName))
                return null;

            BizEntities dbContext = new BizEntities();
            Sys_User user = dbContext.Sys_User.FirstOrDefaultAsync(q => q.UserName == userName).Result;

            IdentityLoginUser identityLoginUser = new IdentityLoginUser
            {
                UserId = user.UserId,
                UserName = user.UserName,
                Password = user.Password
            };

            return Task.FromResult<IdentityLoginUser>(identityLoginUser);
        }

        public Task UpdateAsync(IdentityLoginUser user)
        {
            throw new NotImplementedException("不支持刪除用戶");
        }

        public Task<IdentityLoginUser> FindByIdAsync(string userId)
        {
            if (string.IsNullOrEmpty(userId))
                return null;

            return this.FindByIdAsync(int.Parse(userId));
        }

        public void Dispose() { }

        #region IUserPasswordStore接口
        /// <summary>
     /// 這個方法用不到!!!
        /// 雖然對於普通系統的明文方式這些用不到。
        /// 但是!!!MVC5之后的Identity2.0后數據庫保存的密碼是加密鹽加密過的,這里也需要一並處理,然而1.0並不需要處理,需注意!!!
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        public Task<string> GetPasswordHashAsync(IdentityLoginUser user)
        {
            return Task.FromResult<string>(user.Password);
        }

        public Task<bool> HasPasswordAsync(IdentityLoginUser user)
        {
            return Task.FromResult<bool>(!string.IsNullOrWhiteSpace(user.Password));
        }

        public Task SetPasswordHashAsync(IdentityLoginUser user, string passwordHash)
        {
            user.Password = passwordHash;
            return Task.FromResult<int>(0);
        }

        #endregion
    }
}
自定義UserStore

 但是,此時運行代碼會報錯:Base-64 字符數組或字符串的長度無效。這個是由於微軟的UserManager下的IPasswordHasher是通過MD5+隨機鹽加密出來的。所以,我們還要將密碼加密,對比這一套換成我們自己的!你也可以自己去研究一下加密鹽,我看微軟的源代碼是沒看懂啦,我以為鹽既然是隨機生成的,應該是放在數據庫里啊,但是數據庫里沒有,不懂!

 

自定義PasswordHasher,用自己的密碼

using Microsoft.AspNet.Identity;

namespace Biz.Framework.AspNet.Identity
{
    public class BizPasswordHasher : PasswordHasher
    {
        /// <summary>
        /// 加密密碼,可以采用簡單的MD5加密,也可以通過MD5+隨機鹽加密
        /// </summary>
        /// <param name="password"></param>
        /// <returns></returns>
        public override string HashPassword(string password)
        {
            return password;
        }

        public override PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
        {
            //此處什么加密都不做!
            if (hashedPassword == providedPassword)
            //if (Crypto.VerifyHashedPassword(hashedPassword, providedPassword))
            {
                return PasswordVerificationResult.Success;
            }
            return PasswordVerificationResult.Failed;
        }
    }
}
自定義PasswordHasher
namespace Biz.Web.Controllers
{
    [Authorize]
    public class AccountController : Controller
    {
        public AccountController()
            : this(new UserManager<IdentityLoginUser>(new BizUserStore()))
        {
        }

        public AccountController(UserManager<IdentityLoginUser> userManager)
        {
            UserManager = userManager;
            userManager.PasswordHasher = new BizPasswordHasher();
        }
UserManager中的PasswordHasher也要一並重賦

 但是,判斷用戶通過后,登錄代碼又報錯:System.ArgumentNullException: 值不能為 null。重寫ClaimsIdentityFactory后發現是User.Id為null,有次IdentityLogin中繼承自IUser的Id必須賦值,可以用用戶表中的UserId。

using Microsoft.AspNet.Identity;
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;

namespace Biz.Framework.AspNet.Identity
{
    //public class BizClaimsIdentityFactory<TUser> : IClaimsIdentityFactory<TUser> where TUser : class, IUser
    public class BizClaimsIdentityFactory<TUser> : ClaimsIdentityFactory<TUser, string> where TUser : class, IUser<string>
    {
        internal const string DefaultIdentityProviderClaimValue = "ASP.NET Identity";
        internal const string IdentityProviderClaimType = "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider";

        public string RoleClaimType { get; set; }

        public string UserIdClaimType { get; set; }

        public string UserNameClaimType { get; set; }

        public BizClaimsIdentityFactory()
        {
            this.RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role";
            this.UserIdClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
            this.UserNameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name";
        }


        public async override Task<ClaimsIdentity> CreateAsync(UserManager<TUser, string> manager, TUser user, string authenticationType)
        {
            if (manager == null)
            {
                throw new ArgumentNullException("manager");
            }
            if (user == null)
            {
                throw new ArgumentNullException("user");
            }
            ClaimsIdentity id = new ClaimsIdentity(authenticationType, ((BizClaimsIdentityFactory<TUser>)this).UserNameClaimType, ((BizClaimsIdentityFactory<TUser>)this).RoleClaimType);
            id.AddClaim(new Claim(((BizClaimsIdentityFactory<TUser>)this).UserIdClaimType, user.Id, "http://www.w3.org/2001/XMLSchema#string"));
            id.AddClaim(new Claim(((BizClaimsIdentityFactory<TUser>)this).UserNameClaimType, user.UserName, "http://www.w3.org/2001/XMLSchema#string"));
            id.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"));
            if (manager.SupportsUserRole)
            {
                IList<string> roles = await manager.GetRolesAsync(user.Id);
                using (IEnumerator<string> enumerator = roles.GetEnumerator())
                {
                    while (enumerator.MoveNext())
                    {
                        string current = enumerator.Current;
                        id.AddClaim(new Claim(((BizClaimsIdentityFactory<TUser>)this).RoleClaimType, current, "http://www.w3.org/2001/XMLSchema#string"));
                    }
                }
            }
            if (manager.SupportsUserClaim)
            {
                //ClaimsIdentity identity3;
                //ClaimsIdentity identity2 = id;
                //IList<Claim> claims = await manager.GetClaimsAsync(user.Id);
                //identity3.AddClaims(claims);
            }
            return id;
        }


        //public async override Task<ClaimsIdentity> CreateAsync(UserManager<TUser> manager, TUser user, string authenticationType)
        //{
        //    if (manager == null)
        //    {
        //        throw new ArgumentNullException("manager");
        //    }
        //    if (user == null)
        //    {
        //        throw new ArgumentNullException("user");
        //    }
        //    ClaimsIdentity id = new ClaimsIdentity(authenticationType, ((BizClaimsIdentityFactory<TUser>)this).UserNameClaimType, ((BizClaimsIdentityFactory<TUser>)this).RoleClaimType);
        //    id.AddClaim(new Claim(((BizClaimsIdentityFactory<TUser>)this).UserIdClaimType, user.Id, "http://www.w3.org/2001/XMLSchema#string"));
        //    id.AddClaim(new Claim(((BizClaimsIdentityFactory<TUser>)this).UserNameClaimType, user.UserName, "http://www.w3.org/2001/XMLSchema#string"));
        //    id.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"));
        //    if (manager.SupportsUserRole)
        //    {
        //        IList<string> roles = await manager.GetRolesAsync(user.Id);
        //        using (IEnumerator<string> enumerator = roles.GetEnumerator())
        //        {
        //            while (enumerator.MoveNext())
        //            {
        //                string current = enumerator.Current;
        //                id.AddClaim(new Claim(((BizClaimsIdentityFactory<TUser>)this).RoleClaimType, current, "http://www.w3.org/2001/XMLSchema#string"));
        //            }
        //        }
        //    }
        //    if (manager.SupportsUserClaim)
        //    {
        //        //ClaimsIdentity identity3;
        //        //ClaimsIdentity identity2 = id;
        //        //IList<Claim> claims = await manager.GetClaimsAsync(user.Id);
        //        //identity3.AddClaims(claims);
        //    }
        //    return id;
        //}
    }
}
重寫ClaimsIdentityFactory,注意可以不重寫,此處是為了驗證問題

 

自此,本文結束!需要代碼的可留言

 

其他的一些可借鑒的文章:

重寫UserManager

加密鹽


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM