2010-04-10 200 views

回答

4

標準成員資格提供程序API的替代實現不能替代,因爲它基於非通用接口。

但是,實際上整個成員資格API只增加了很少的價值,並且很容易推出自己的解決方案。

爲了幫助您在這裏開始使用一些我通常使用的幫助類。

首先,一些代碼來處理Cookie和Forms身份驗證:

public interface ISecurityPersistenceProvider 
{ 
    void SetAuthCookie(int version, string name, DateTime expireDate, bool persistent, string cookieData); 
    HttpCookie GetAuthCookie(); 
    string GetAuthCookieValue(out string name); 
    void RemoveAuthCookie(); 
} 

public class FormsPersistenceProvider : ISecurityPersistenceProvider 
{ 
    public void SetAuthCookie(int version, string name, DateTime expireDate, bool persistent, string cookieData) 
    { 
     var ticket = new FormsAuthenticationTicket(version, name, DateTime.Now, expireDate, persistent, cookieData); 
     string secureTicket = FormsAuthentication.Encrypt(ticket); 
     var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, secureTicket); 
     cookie.Expires = ticket.Expiration; 
     HttpContext.Current.Response.Cookies.Add(cookie); 
    } 

    public HttpCookie GetAuthCookie() 
    { 
     HttpCookie cookie = HttpContext.Current.Request.Cookies[ FormsAuthentication.FormsCookieName ]; 
     return cookie; 
    } 

    public string GetAuthCookieValue(out string name) 
    { 
     HttpCookie cookie = GetAuthCookie(); 
     FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value); 
     name = ticket.Name; 
     return ticket.UserData; 
    } 

    public void RemoveAuthCookie() 
    { 
     FormsAuthentication.SignOut(); 
    } 
} 

二是一些代碼來驗證用戶對數據存儲。你需要爲這個接口提供你自己的實現(和/或根據你的需要進行調整)。

public interface ISecurityUserProvider<T> where T : class 
{ 
    T AuthenticateUser(string customer, string userName, string passwordHash); 
    T GetUser(string customer, string userName); 
    string[] GetRoles(T user); 
} 

第三,一個類來讓我們回到呼叫者的附加信息(這將通過下面的安全管理器類中使用):

public class AuthenticationResult 
{ 
    #region Fields/Properties 
    public bool Success { get; internal set; } 
    public string Message { get; internal set; } 
    public IDictionary<string,string> Errors { get; internal set; } 
    #endregion 

    #region Constructors 
    public AuthenticationResult(bool success, string message) : this(success, message, null) 
    { 
    } 
    public AuthenticationResult(bool success, string message, IDictionary<string, string> errors) 
    { 
     Success = success; 
     Message = message; 
     Errors = errors ?? new Dictionary<string, string>(); 
    } 
    #endregion 
} 

我還使用自定義的主類,允許我將自定義對象與基本IPrincipal實現相關聯。這僅僅是一個快速提取給你的想法:

public class CodeworksPrincipal<TUserData> : IPrincipal where TUserData : class 
{ 
    private string name; 
    private string[] roles; 
    private string allRoles; 
    private TUserData userData; 

    public CodeworksPrincipal(string name, string[] roles, TUserData userData) 
    { 
     // init fields, etc. 
    } 
} 

最後,用大量的邏輯SecurityManager類:

public class SecurityManager<T,TPersistenceProvider,TUserProvider> 
    where T : class 
    where TPersistenceProvider : ISecurityPersistenceProvider 
    where TUserProvider : ISecurityUserProvider<T> 
{ 
    private readonly ISecurityPersistenceProvider persistenceProvider; 
    private readonly ISecurityUserProvider<T> userProvider; 
    // NOTE this constant is used to validate the validity of the cookie (see below) 
    private const int CookieParameterCount = 3; 

    public SecurityManager(ISecurityPersistenceProvider persistenceProvider, ISecurityUserProvider<T> userProvider) 
    { 
     this.persistenceProvider = persistenceProvider; 
     this.userProvider = userProvider; 
    } 

    #region Properties 
    protected ISecurityPersistenceProvider PersistenceProvider 
    { 
     get { return persistenceProvider; } 
    } 
    protected ISecurityUserProvider<T> UserProvider 
    { 
     get { return userProvider; } 
    } 
    public IIdentity CurrentIdentity 
    { 
     get { return Thread.CurrentPrincipal.Identity; } 
    } 
    public bool IsAuthenticated 
    { 
     get 
     { 
      IPrincipal principal = Thread.CurrentPrincipal; 
      return principal != null && principal.Identity != null && principal.Identity.IsAuthenticated; 
     } 
    } 
    public bool IsInRole(string roleName) 
    { 
     IPrincipal principal = Thread.CurrentPrincipal; 
     return IsAuthenticated && principal.IsInRole(roleName); 
    } 
    public string UserName 
    { 
     get { return IsAuthenticated ? CurrentIdentity.Name : ""; } 
    } 
    #endregion 

    #region Authentication 
    public AuthenticationResult Authenticate(string userName, string password, bool persistent, string visitorAddress) 
    { 
     return Authenticate(null, userName, password, persistent, visitorAddress); 
    } 
    public AuthenticationResult Authenticate(string customer, string userName, string password, bool persistent, string visitorAddress) 
    { 
     AuthenticationResult result = ValidateInput(userName, password); 
     if(! result.Success) 
      return result; 
     string passwordHash = GetCryptographicHash(password); 
     T user = userProvider.AuthenticateUser(customer, userName, passwordHash); 
     if(user == null) 
      return new AuthenticationResult(false, "Unable to login using the specified credentials.", null); 
     if(! IsAuthorizedVisitor(user, visitorAddress)) 
      return new AuthenticationResult(false, "Credentials do not allow login from your current IP address.", null); 
     CurrentPrincipal = new CodeworksPrincipal<T>(userName, userProvider.GetRoles(user), user); 
     // remember to change CookieParameterCount if you change parameter count here 
     string cookieData = String.Format("{0}|{1}|{2}", CurrentIdentity.Name, customer, CurrentPrincipal.AllRoles); 
     persistenceProvider.SetAuthCookie(1, userName, DateTime.Now.AddMonths(1), persistent, cookieData); 
     // TODO create an audit log entry for the current request 
     return new AuthenticationResult(true, null, null); 
    } 
    private AuthenticationResult ValidateInput(string login, string password) 
    { 
     var result = new AuthenticationResult(true, "Invalid or missing credentials."); 
     if(String.IsNullOrEmpty(login)) 
     { 
      result.Success = false; 
      result.Errors.Add("login", "You must specify an alias or email address."); 
     } 
     if(String.IsNullOrEmpty(password)) 
     { 
      result.Success = false; 
      result.Errors.Add("password", "You must specify a password."); 
     } 
     // TODO add message to inform users of other requirements. 
     return result;    
    } 
    #endregion 

    #region Cookie Authentication 
    public void CookieAuthenticate(string visitorAddress) 
    { 
     HttpCookie cookie = persistenceProvider.GetAuthCookie(); 
     if(cookie != null) 
     { 
      string userName; 
      string userData = persistenceProvider.GetAuthCookieValue(out userName); 
      string[] cookieData = userData.Split('|'); 
      // extract data from cookie 
      bool isValid = cookieData.Length == CookieParameterCount && 
          ! string.IsNullOrEmpty(cookieData[ 0 ]) && 
          cookieData[ 0 ] == userName && 
          IsAuthorizedVisitor(cookieData[ 1 ], cookieData[ 0 ], visitorAddress); 
      if(isValid) 
      { 
       string customer = cookieData[ 1 ]; 
       string[] roles = cookieData[ 2 ].Split(','); 
       T user = userProvider.GetUser(customer, userName); 

       CurrentPrincipal = new CodeworksPrincipal<T>(userName, roles, user); 
      } 
     } 
    } 
    #endregion 

    #region Logout 
    public void Logout() 
    { 
     // logout the current user 
     if(HttpContext.Current.Request.IsAuthenticated || CurrentPrincipal != null) 
     { 
      long address = GetAddressFromString(HttpContext.Request.UserHostAddress); 
      // TODO add audit log entry 
      // sign out current user 
      persistenceProvider.RemoveAuthCookie(); 
      CurrentPrincipal = null; 
     } 
    } 
    #endregion 

    #region VisitorAddress Checks 
    public long GetAddressFromString(string address) 
    { 
     IPAddress ipAddress; 
     if(IPAddress.TryParse(address, out ipAddress)) 
     { 
      byte[] segments = ipAddress.GetAddressBytes(); 
      long result = 0; 
      for(int i = 0; i < segments.Length; i++) 
      { 
       result += segments[ i ] << (i * 8); 
      } 
      return result; 
     } 
     return long.MaxValue; 
    } 

    public bool IsAuthorizedVisitor(string customer, string userName, string visitorAddress) 
    { 
     return IsAuthorizedVisitor(customer, userName, GetAddressFromString(visitorAddress)); 
    } 

    public bool IsAuthorizedVisitor(string customer, string userName, long visitorAddress) 
    { 
     T user = userProvider.GetUser(customer, userName); 
     return IsAuthorizedVisitor(user, visitorAddress); 
    } 

    public bool IsAuthorizedVisitor(T user, string visitorAddress) 
    { 
     return IsAuthorizedVisitor(user, GetAddressFromString(visitorAddress)); 
    } 

    public bool IsAuthorizedVisitor(T user, long visitorAddress) 
    { 
     if(user == null || visitorAddress == 0) 
      return false; 
     if(user.Hosts.Count == 0) 
      return true; 
     foreach(Host host in user.Hosts) 
     { 
      if(IsAuthorizedVisitor(host.HostAddress, host.HostMask, visitorAddress)) 
       return true; 
     } 
     return true; 
    } 

    public bool IsAuthorizedVisitor(long allowedAddress, long allowedMask, long visitorAddress) 
    { 
     long requireMask = allowedAddress & allowedMask; 
     return (visitorAddress & requireMask) == requireMask; 
    } 
    #endregion 

    #region Cryptographic Helpers 
    public static string GetCryptographicHash(string password) 
    { 
     SHA256 hash = SHA256.Create(); 
     byte[] input = Encoding.UTF8.GetBytes(password); 
     byte[] output = hash.ComputeHash(input); 
     return Convert.ToBase64String(output); 
    } 
    #endregion 
} 

可以剝離出位,檢查訪客主機地址,如果你不需要它。

我希望這可以幫助,即使它本身不是會員供應商;-)

+0

這太棒了。但是我會留下這個問題,因爲我還需要數據庫連接方面的幫助。謝謝 – Luke101 2010-04-12 21:54:57

+0

你應該爲此發佈一個單獨的問題,因爲它不是真正的認證相關。 您只需要實現ISecurityUserProvider接口的數據庫方法。 – 2010-04-13 14:04:51