注意,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運用了橋接模式,查找用戶的方法來自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); } } }
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 } }
但是,此時運行代碼會報錯: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; } } }

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(); }
但是,判斷用戶通過后,登錄代碼又報錯: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; //} } }
自此,本文結束!需要代碼的可留言
其他的一些可借鑒的文章:
加密鹽