角色提供者的替代品?
我试图避免使用角色提供者和会员提供者,因为我认为它太笨拙了,因此我试图制作我自己的“版本”,它不笨拙,更易于管理/灵活。 现在是我的问题..是否有一个替代角色提供者是体面的? (我知道我可以做自定义角色provier,会员供应商等)
通过更易于管理/灵活的方式,我的意思是我仅限于使用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.
}
}
这样,我们就不会在当前用户的会话中公开密码散列和所有其他详细信息,因为它们在用户的会话生存期中确实不需要。
我在这里根据@TheCloudlessSky发布了一个角色提供者。 我认为我可以添加和分享我所做的事情的事情很少。 首先,如果要将RequirepPermission
类用作动作过滤器作为属性,则需要为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
会话
public class UserSession
{
public string UserName { get; set; }
public IEnumerable<string> UserRoles { get; set; }
}
另一点是FormsAuthorizationService
类以及我们如何将用户分配给httpContext.Session["CurrentUser"]
。 我在这种情况下的方法是创建一个新的userSession类实例,并直接将用户从httpContext.User.Identity.Name
给userSession变量,如您在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));
}
}
那么在用户通过身份验证后,在控制器中可以从数据库获取角色并将其分配给角色会话:
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");
在这种模式中的警告是,当用户登录到系统时,如果角色分配给用户,授权不起作用,除非他注销并重新登录到系统中。
另一件事是,没有必要为角色分配一个类,因为我们可以直接从数据库获取角色,并将其设置为控制器中的角色会话。
完成所有这些代码之后,最后一步是将此属性绑定到控制器中的方法:
[RequirePermission("Admin,DM")]
public ActionResult Create()
{
return View();
}
如果您使用Castle Windsor依赖注入,您可以注入RoleProviders列表,这些列表可用于确定您选择实施的任何来源的用户权限。
http://ivida.co.uk/2011/05/18/mvc-getting-user-roles-from-multiple-sources-register-and-resolve-arrays-of-dependencis-using-the-fluent-api/
链接地址: http://www.djcxy.com/p/90159.html上一篇: Alternative to Role Provider?
下一篇: Multiple role based folder authorization in asp.net web.config