ASP.NETMVC-角色提供程序的替代是什么?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (40)

我试图避免使用角色提供者和会员提供者,因为我认为它太笨拙了,因此我试图制作我自己的“版本”,它不笨拙,更易于管理/灵活。现在是我的问题..是否有一个替代角色提供者是体面的?(我知道我可以做自定义角色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.
    }
}

这样,我们就不会在当前用户的会话中公开密码散列和所有其他详细信息,因为它们在用户的会话生存期中确实不需要。

用户回答回答于

我认为我可以添加和分享我所做的事情的事情很少。首先,如果想将RequirepPermission动作过滤器的类用作属性,则需要为ActionFilterAttribute类实现RequirepPermission类。

接口类IAuthenticationServiceIAuthorizationService

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"]。我在这种情况下的方法是创建一个新的userSession类实例,并直接将用户从httpContext.User.Identity.NameuserSession变量分配,就像在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();
}

扫码关注云+社区