ASP.NET MVC – 替代angular色提供者?
我试图避免使用angular色提供者和会员提供者,因为我觉得它太笨拙了,所以我正在努力制作我自己的“版本”,这个版本不那么笨拙,更容易pipe理/灵活。 现在是我的问题..是否有一个替代品的angular色提供者是不错的? (我知道我可以做自定义angular色provier,会员供应商等)
通过更易于pipe理/灵活的我的意思是,我只能使用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() { // ... }
这种方法的优点是你也可以使用dependency injection和一个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. } }
这样,我们就不会在当前用户的会话中暴露密码散列和所有其他细节,因为它们在用户的会话生存期中确实不需要。
我在这里根据@TheCloudlessSky发布了一个angular色提供者。 我想我可以添加和分享我所做的一些事情。 首先,如果要将RequirepPermission
类用作动作filter作为属性,则需要为RequirepPermission
类实现ActionFilterAttribute
类。
接口类IAuthenticationService
和IAuthorizationService
public interface IAuthenticationService { void SignIn(string userName, bool createPersistentCookie); void SignOut(); } public interface IAuthorizationService { bool Authorize(UserSession user, string[] requiredRoles); }
FormsAuthenticationService
类
/// <summary> /// This class is for Form Authentication /// </summary> public class FormsAuthenticationService : IAuthenticationService { public void SignIn(string userName, bool createPersistentCookie) { if (String.IsNullOrEmpty(userName)) throw new ArgumentException(@"Value cannot be null or empty.", "userName"); FormsAuthentication.SetAuthCookie(userName, createPersistentCookie); } public void SignOut() { FormsAuthentication.SignOut(); } }
UserSession
calss
public class UserSession { public string UserName { get; set; } public IEnumerable<string> UserRoles { get; set; } }
另一点是FormsAuthorizationService
类以及我们如何将用户分配给httpContext.Session["CurrentUser"]
。 我在这种情况下的方法是创build一个新的userSession类的实例,并直接将用户从httpContext.User.Identity.Name
给userSessionvariables,您可以在FormsAuthorizationService
类中看到。
[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method, Inherited = false)] public class RequirePermissionAttribute : ActionFilterAttribute, IAuthorizationFilter { #region Fields private readonly IAuthorizationService _authorizationService; private readonly string[] _permissions; #endregion #region Constructors public RequirePermissionAttribute(string requiredRoles) { _permissions = requiredRoles.Trim().Split(',').ToArray(); _authorizationService = null; } #endregion #region Methods private IAuthorizationService CreateAuthorizationService(HttpContextBase httpContext) { return _authorizationService ?? new FormsAuthorizationService(httpContext); } public void OnAuthorization(AuthorizationContext filterContext) { var authSvc = 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. if (filterContext.HttpContext.Session == null) return; if (filterContext.HttpContext.Request == null) return; var success = false; if (filterContext.HttpContext.Session["__Roles"] != null) { var rolesSession = filterContext.HttpContext.Session["__Roles"]; var roles = rolesSession.ToString().Trim().Split(',').ToList(); var userSession = new UserSession { UserName = filterContext.HttpContext.User.Identity.Name, UserRoles = roles }; success = authSvc.Authorize(userSession, _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 = OnCacheAuthorization(new HttpContextWrapper(context)); }, null); } else { HandleUnauthorizedRequest(filterContext); } } private static 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(); } } private HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext) { var authSvc = CreateAuthorizationService(httpContext); if (httpContext.Session != null) { var success = false; if (httpContext.Session["__Roles"] != null) { var rolesSession = httpContext.Session["__Roles"]; var roles = rolesSession.ToString().Trim().Split(',').ToList(); var userSession = new UserSession { UserName = httpContext.User.Identity.Name, UserRoles = roles }; success = authSvc.Authorize(userSession, _permissions); } return success ? HttpValidationStatus.Valid : HttpValidationStatus.IgnoreThisRequest; } return 0; } #endregion } internal class FormsAuthorizationService : IAuthorizationService { private readonly HttpContextBase _httpContext; public FormsAuthorizationService(HttpContextBase httpContext) { _httpContext = httpContext; } public bool Authorize(UserSession userSession, string[] requiredRoles) { return userSession.UserRoles.Any(role => requiredRoles.Any(item => item == role)); } }
那么在用户通过身份validation后,在控制器中可以从数据库获取angular色并将其分配给angular色会话:
var roles = Repository.GetRolesByUserId(Id); if (ControllerContext.HttpContext.Session != null) ControllerContext.HttpContext.Session.Add("__Roles",roles); FormsService.SignIn(collection.Name, true);
用户注销系统后,您可以清除会话
FormsService.SignOut(); Session.Abandon(); return RedirectToAction("Index", "Account");
在这种模式下的警告是,当用户login到系统时,如果angular色分配给用户,授权不起作用,除非他注销并重新login到系统中。
另一件事是没有必要为angular色分配一个类,因为我们可以直接从数据库获取angular色,并将其设置为控制器中的angular色会话。
完成所有这些代码之后,最后一步是将此属性绑定到控制器中的方法:
[RequirePermission("Admin,DM")] public ActionResult Create() { return View(); }
如果您使用Castle Windsordependency injection,您可以注入RoleProviders列表,这些列表可以用来确定您select实施的任何来源的用户权限。
您不需要为angular色使用静态类。 例如, SqlRoleProvider允许你定义数据库中的angular色。
当然,如果你想从你自己的服务层中检索angular色,创build你自己的angular色提供者并不难,但实际上并没有太多的方法可以实现。
您可以通过覆盖适当的接口来实现自己的成员资格和angular色提供者。
如果你想从头开始,通常这些types的东西被实现为一个自定义的http模块 ,它将用户凭证存储在httpcontext或会话中。 无论哪种方式,你可能会想用某种身份validation令牌来设置cookie。