iT邦幫忙

DAY 4
0

第一次設計架構就上手系列 第 4

第一次設計架構就上手 - 從登入開始

從登入開始

雞隻健康追蹤資訊系統的第一道關卡就是讓使用者登入,由於系統是建立在ASP.NET MVC 5之上,因此對於使用者的登入及權限控管的機制都將採用ASP.NET Identity。如果透過VS2013新建立一個MVC專案,可以發現裡頭已經包含了一個基本的登入驗證機制,但是我們都知道,現實世界的需求不會如同範例這麼地單純,資料庫不一定會用MS SQL Server,會員資料也會需要記錄許多額外重要資訊。因此接下來將介紹該如何手工打造出利用ASP.NET Identity的登入及驗證機制。

首先,我們先在資料庫裡建立起使用者及權限相關的資料表。

CREATE TABLE [dbo].[User] (
 [Id] varchar(45) NOT NULL PRIMARY KEY, --GUID
 [UserId] varchar(45) NOT NULL, --使用者登入帳號
 [PasswordHash] varchar(100) NOT NULL, --使用者登入密碼
 [Name] varchar(10) NOT NULL, --使用者名稱
 [GroupId] varchar(45), --使用者群組別
 [EmpNo] varchar(10) NOT NULL --員工編號
)

CREATE TABLE [dbo].[Roles] (
 [Id] varchar(45) NOT NULL PRIMARY KEY, --GUID
 [Name] varchar(200) NOT NULL
)

CREATE TABLE [dbo].[UserGroup] (
 [Id] varchar(45) NOT NULL PRIMARY KEY, --GUID
 [Name] varchar(50) NOT NULL
)

CREATE TABLE [dbo].[GroupRoles] (
 [GroupId] varchar(45) NOT NULL, 
 [RoleId] varchar(45) NOT NULL
)

接著建立起對應的ApplicationUser類別並且繼承至Microsoft.AspNet.Identity.IUser

public class ApplicationUser : IUser
{
 public ApplicationUser()
 {
  this.Id = Guid.NewGuid().ToString();
 }

 public ApplicationUser(string userId) : this()
 {
  this.UserId = userId;
 }
 
 public string Id { get; set; }
 
 public string UserId { get; set; }
 
 public string PasswordHash { get; set; }
 
 public string Name { get; set; }

 public string GroupId { get; set; }
 
 public string EmpNo { get; set; }

 public string SecurityStamp { get; set; }
}

ASP.NET Identity把所有的操作都包裝在一個叫UserStore的類別內,要使用ASP.NET Identity哪些功能,如Password、Role或Claim等機制,皆看UserStore是否有實作該項介面而定。

public class UserStore : IUserStore<ApplicationUser>,
       IUserPasswordStore<ApplicationUser>,
       IUserRoleStore<ApplicationUser>
{
 private UserRepository userRepository;
 private UserRolesRepository userRolesRepository;

 public UserStore(string farmId)
 {
  RepositoryFactory repositoryFactory = RepositoryFactory.GetFactory(farmId);
  this.userRepository = repositoryFactory.Get<UserRepository>();
  this.userRolesRepository = repositoryFactory.Get<UserRolesRepository>();
 }

 public Task CreateAsync(ApplicationUser user)
 {
  if (user == null)
  {
   throw new ArgumentNullException("user");
  }

  this.userRepository.Insert(user);

  return Task.FromResult<object>(null);
 }

 public Task DeleteAsync(ApplicationUser user)
 {
  if (user != null)
  {
   this.userRepository.Delete(user.Id);
  }

  return Task.FromResult<object>(null);
 }

 public Task<ApplicationUser> FindByIdAsync(string id)
 {
  if (string.IsNullOrEmpty(id))
  {
   throw new ArgumentException("Null or empty argument: id");
  }

  ApplicationUser user = this.userRepository.Select(id);
  if (user != null)
  {
   return Task.FromResult<ApplicationUser>(user);
  }

  return Task.FromResult<ApplicationUser>(null);
 }

 public Task<ApplicationUser> FindByNameAsync(string userId)
 {
  if (string.IsNullOrEmpty(userId))
  {
   throw new ArgumentException("Null or empty argument: userId");
  }

  ApplicationUser user = this.userRepository.SelectByUserName(userId);
  if (user != null)
  {
   return Task.FromResult<ApplicationUser>(user);
  }

  return Task.FromResult<ApplicationUser>(null);
 }

 public Task<string> GetPasswordHashAsync(ApplicationUser user)
 {
  if (user == null)
  {
   throw new ArgumentNullException("user");
  }

  return Task.FromResult<string>(user.PasswordHash);
 }

 public Task<IList<string>> GetRolesAsync(ApplicationUser user)
 {
  if (user == null)
  {
   throw new ArgumentNullException("user");
  }

  IList<string> roles = this.userRolesRepository.Select(user.Id);
  {
   if (roles != null)
   {
    return Task.FromResult<IList<string>>(roles);
   }
  }

  return Task.FromResult<IList<string>>(null);
 }

 public Task<bool> HasPasswordAsync(ApplicationUser user)
 {
  if (user == null)
  {
   throw new ArgumentNullException("user");
  }

  var hasPassword = !string.IsNullOrEmpty(this.userRepository.Select(user.Id).PasswordHash);

  return Task.FromResult<bool>(hasPassword);
 }

 public Task<bool> IsInRoleAsync(ApplicationUser user, string role)
 {
  if (user == null)
  {
   throw new ArgumentNullException("user");
  }

  if (string.IsNullOrEmpty(role))
  {
   throw new ArgumentNullException("role");
  }

  IEnumerable<string> roles = this.userRolesRepository.Select(user.Id);
  if (roles != null && roles.Contains(role))
  {
   return Task.FromResult<bool>(true);
  }

  return Task.FromResult<bool>(false);
 }

 public Task SetPasswordHashAsync(ApplicationUser user, string passwordHash)
 {
  if (user == null)
  {
   throw new ArgumentNullException("user");
  }

  user.PasswordHash = passwordHash;
  return Task.FromResult<object>(null);
 }

 public Task UpdateAsync(ApplicationUser user)
 {
  if (user == null)
  {
   throw new ArgumentNullException("user");
  }

  this.userRepository.Update(user);

  return Task.FromResult<object>(null);
 }
 
 public Task RemoveFromRoleAsync(ApplicationUser user, string role)
 {
  throw new NotImplementedException();
 }
 
 public Task AddToRoleAsync(ApplicationUser user, string role)
 {
  throw new NotImplementedException();
 }
 
 public void Dispose()
 {
  this.Dispose(true);

  GC.SuppressFinalize(this);
 }

 private void Dispose(bool disposing)
 {
  // do nothing. 
 }
}

上面的RepositoryFactory,是系統架構中對儲存層的存取方式,之後會再做更詳細的說明,現在可以先想像成可以直接取得資料庫中的User及Role的資料。

實際上我們並不會直接使用UserStore,在ASP.NET Identity的機制中,還需要透過UserManager類別來呼叫,我們再將它的操作包裝成IdentityHelper以簡化新增使用者、登入及登出等操作。

public class IdentityHelper : IDisposable
{
 private UserManager<ApplicationUser> userManager;

 public IdentityHelper(string farmId)
 {
  this.userManager = new UserManager<ApplicationUser>(new UserStore(farmId));
 }

 private static IAuthenticationManager AuthenticationManager
 {
  get
  {
   return HttpContext.Current.GetOwinContext().Authentication;
  }
 }

 public static void LogOff()
 {
  AuthenticationManager.SignOut();
 }

 public IdentityResult Register(ApplicationUser user, string password)
 {
  return this.userManager.Create(user, password);
 }

 public IdentityResult Register(string userId, string password, string name)
 {
  return this.Register(
   new ApplicationUser()
   {
    UserId = userId,
    Name = name,
   }, 
   password);
 }

 public void SignIn(ApplicationUser user)
 {
  AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
  var identity = this.userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
  AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = false }, identity);
 }

 public bool SignIn(string userId, string password)
 {
  ApplicationUser user = this.userManager.Find(userId, password);

  if (user == null)
  {
   return false;
  }

  this.SignIn(user);
  return true;
 }

 public void Dispose()
 {
  this.Dispose(true);
 }

 protected virtual void Dispose(bool flag)
 {
  this.userManager.Dispose();
  GC.SuppressFinalize(this);
 }
}

基本上,我們現在已經有了一個透過ASP.NET Identity來做新增使用者、登入及登出系統的基礎架構了。


上一篇
第一次設計架構就上手 - 資料庫架構
下一篇
第一次設計架構就上手 - 用Identity存放使用者資料
系列文
第一次設計架構就上手7

尚未有邦友留言

立即登入留言