Asp.net

ASP.NET MVC - 角色提供者的替代品?

  • January 29, 2011

我試圖避免使用 Role Provider 和 Membership Provider,因為在我看來它太笨拙了,因此我試圖製作我自己的“版本”,它不那麼笨拙,更易於管理/靈活。現在是我的問題.. 有沒有一個不錯的角色提供者的替代品?(我知道我可以做自定義角色提供者、會員提供者等)

通過更易於管理/更靈活,我的意思是我僅限於使用 Roles 靜態類,而不是直接在與數據庫上下文互動的服務層中實現,而是我必須使用具有自己的數據庫上下文的 Roles 靜態類等等,表名也很糟糕..

提前致謝。

我和你在同一條船上 - 我一直討厭 RoleProviders。是的,如果您想為一個小型網站啟動並執行它們,它們很棒,但它們不是很現實。我一直發現的主要缺點是它們將您直接綁定到 ASP.NET。

我在最近的一個項目中採用的方法是定義幾個介面,它們是服務層的一部分(注意:我對它們進行了相當多的簡化——但你可以輕鬆地添加它們):

public interface IAuthenticationService
{
   bool Login(string username, string password);
   void Logout(User user);
}

public interface IAuthorizationService
{
   bool Authorize(User user, Roles requiredRoles);
}

然後你的使用者可以有一個Roles枚舉:

public enum Roles
{
   Accounting = 1,
   Scheduling = 2,
   Prescriptions = 4
   // What ever else you need to define here.
   // Notice all powers of 2 so we can OR them to combine role permissions.
}

public class User
{
   bool IsAdministrator { get; set; }
   Roles Permissions { get; set; }
}

對於您的IAuthenticationService,您可以有一個執行標準密碼檢查的基本實現,然後您可以有一個FormsAuthenticationService做更多的事情,例如設置 cookie 等。對於您的AuthorizationService,您需要這樣的東西:

public class AuthorizationService : IAuthorizationService
{
   public bool Authorize(User userSession, Roles requiredRoles)
   {
       if (userSession.IsAdministrator)
       {
           return true;
       }
       else
       {
           // Check if the roles enum has the specific role bit set.
           return (requiredRoles & user.Roles) == requiredRoles;
       }
   }
}

在這些基礎服務之上,您可以輕鬆添加服務來重置密碼等。

由於您使用的是 MVC,因此您可以使用以下命令在操作級別進行授權ActionFilter

public class RequirePermissionFilter : IAuthorizationFilter
{
   private readonly IAuthorizationService authorizationService;
   private readonly Roles permissions;

   public RequirePermissionFilter(IAuthorizationService authorizationService, Roles requiredRoles)
   {
       this.authorizationService = authorizationService;
       this.permissions = requiredRoles;
       this.isAdministrator = isAdministrator;
   }

   private IAuthorizationService CreateAuthorizationService(HttpContextBase httpContext)
   {
       return this.authorizationService ?? new FormsAuthorizationService(httpContext);
   }

   public void OnAuthorization(AuthorizationContext filterContext)
   {
       var authSvc = this.CreateAuthorizationService(filterContext.HttpContext);
       // Get the current user... you could store in session or the HttpContext if you want too. It would be set inside the FormsAuthenticationService.
       var userSession = (User)filterContext.HttpContext.Session["CurrentUser"];

       var success = authSvc.Authorize(userSession, this.permissions);

       if (success)
       {
           // Since authorization is performed at the action level, the authorization code runs
           // after the output caching module. In the worst case this could allow an authorized user
           // to cause the page to be cached, then an unauthorized user would later be served the
           // cached page. We work around this by telling proxies not to cache the sensitive page,
           // then we hook our custom authorization code into the caching mechanism so that we have
           // the final say on whether or not a page should be served from the cache.
           var cache = filterContext.HttpContext.Response.Cache;
           cache.SetProxyMaxAge(new TimeSpan(0));
           cache.AddValidationCallback((HttpContext context, object data, ref HttpValidationStatus validationStatus) =>
           {
               validationStatus = this.OnCacheAuthorization(new HttpContextWrapper(context));
           }, null);
       }
       else
       {
           this.HandleUnauthorizedRequest(filterContext);
       }
   }

   private void HandleUnauthorizedRequest(AuthorizationContext filterContext)
   {
       // Ajax requests will return status code 500 because we don't want to return the result of the
       // redirect to the login page.
       if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
       {
           filterContext.Result = new HttpStatusCodeResult(500);
       }
       else
       {
           filterContext.Result = new HttpUnauthorizedResult();
       }
   }

   public HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext)
   {
       var authSvc = this.CreateAuthorizationService(httpContext);
       var userSession = (User)httpContext.Session["CurrentUser"];

       var success = authSvc.Authorize(userSession, this.permissions);

       if (success)
       {
           return HttpValidationStatus.Valid;
       }
       else
       {
           return HttpValidationStatus.IgnoreThisRequest;
       }
   }
}

然後您可以在控制器操作上進行裝飾:

[RequirePermission(Roles.Accounting)]
public ViewResult Index()
{
  // ...
}

這種方法的優點是您還可以使用依賴注入和 IoC 容器來連接。此外,您可以在多個應用程序(不僅僅是您的 ASP.NET 應用程序)中使用它。您將使用您的 ORM 來定義適當的模式。

如果您需要有關FormsAuthorization/Authentication服務的更多詳細資訊或從這裡去哪裡,請告訴我。

編輯:要添加“安全修剪”,您可以使用 HtmlHelper 來完成。這可能需要多一點……但你明白了。

public static bool SecurityTrim<TModel>(this HtmlHelper<TModel> source, Roles requiredRoles)
{
   var authorizationService = new FormsAuthorizationService();
   var user = (User)HttpContext.Current.Session["CurrentUser"];
   return authorizationService.Authorize(user, requiredRoles);
}

然後在您的視圖中(在此處使用 Razor 語法):

@if(Html.SecurityTrim(Roles.Accounting))
{
   <span>Only for accounting</span>
}

編輯:UserSession看起來像這樣:

public class UserSession
{
   public int UserId { get; set; }
   public string UserName { get; set; }
   public bool IsAdministrator { get; set; }
   public Roles GetRoles()
   {
        // make the call to the database or whatever here.
        // or just turn this into a property.
   }
}

這樣,我們不會在目前使用者的會話中公開密碼雜湊和所有其他詳細資訊,因為使用者的會話生命週期實際上不需要它們。

引用自:https://stackoverflow.com/questions/4837103