如何为 ASP.NET MVC 2 创建自定义成员资格提供程序?

Posted

技术标签:

【中文标题】如何为 ASP.NET MVC 2 创建自定义成员资格提供程序?【英文标题】:How do I create a custom membership provider for ASP.NET MVC 2? 【发布时间】:2011-02-15 19:09:47 【问题描述】:

如何根据 ASP.NET 成员资格提供程序为 ASP.NET MVC 2 创建自定义成员资格?

【问题讨论】:

【参考方案1】:

我创建了一个包含自定义成员资格提供程序的新项目,并覆盖了 MembershipProvider 抽象类中的 ValidateUser 方法:

public class MyMembershipProvider : MembershipProvider
 
    public override bool ValidateUser(string username, string password)
        
        // this is where you should validate your user credentials against your database.
        // I've made an extra class so i can send more parameters 
        // (in this case it's the CurrentTerritoryID parameter which I used as 
        // one of the MyMembershipProvider class properties). 

        var oUserProvider = new MyUserProvider();  
        return oUserProvider.ValidateUser(username,password,CurrentTerritoryID);
    

然后我通过添加一个引用并从我的 web.config 中指出它来将该提供程序连接到我的 ASP.NET MVC 2 项目:

<membership defaultProvider="MyMembershipProvider">
    <providers>
        <clear />
        <add name="MyMembershipProvider"
            applicationName="MyApp"
            Description="My Membership Provider"
            passwordFormat="Clear"
            connectionStringName="MyMembershipConnection"
            type="MyApp.MyMembershipProvider" />
    </providers>
</membership>

我确实需要创建一个继承 RoleProvider 抽象类并覆盖 GetRolesForUser 方法的自定义类。 ASP.NET MVC Authorizing 使用该方法找出分配给当前登录用户的角色,并确保允许用户访问控制器操作。

以下是我们需要采取的步骤:

1) 创建一个自定义类,继承 RoleProvider 抽象类并重写 GetRolesForUser 方法:

public override string[] GetRolesForUser(string username)

    SpHelper db = new SpHelper();
    DataTable roleNames = null;
    try
    
        // get roles for this user from DB...

        roleNames = db.ExecuteDataset(ConnectionManager.ConStr,
                    "sp_GetUserRoles",
                    new mysqlParameter("_userName", username)).Tables[0];
    
    catch (Exception ex)
    
        throw ex;
    
    string[] roles = new string[roleNames.Rows.Count];
    int counter = 0;
    foreach (DataRow row in roleNames.Rows)
    
        roles[counter] = row["Role_Name"].ToString();
        counter++;
    
    return roles;

2) 通过我们的 web.config 将角色提供者与 ASP.NET MVC 2 应用程序连接起来:

<system.web>
...

<roleManager enabled="true" defaultProvider="MyRoleProvider">
    <providers>
        <clear />
        <add name="MyRoleProvider"
            applicationName="MyApp"
            type="MyApp.MyRoleProvider"
            connectionStringName="MyMembershipConnection" />
    </providers>
</roleManager>

...
</system.web>

3) 在想要的 Controller/Action 上方设置 Authorize(Roles="xxx,yyy"):

[Authorization(Roles = "Customer Manager,Content Editor")]
public class MyController : Controller

    ...... 

就是这样!现在可以了!

4) 可选:设置自定义 Authorize 属性,以便我们可以将不需要的角色重定向到 AccessDenied 页面:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class MyAuthorizationAttribute : AuthorizeAttribute

    /// <summary>
    /// The name of the master page or view to use when rendering the view on authorization failure.  Default
    /// is null, indicating to use the master page of the specified view.
    /// </summary>
    public virtual string MasterName  get; set; 

    /// <summary>
    /// The name of the view to render on authorization failure.  Default is "Error".
    /// </summary>
    public virtual string ViewName  get; set; 

    public MyAuthorizationAttribute ()
        : base()
    
        this.ViewName = "Error";
    

    protected void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
    
        validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
    

    public override void OnAuthorization(AuthorizationContext filterContext)
    
        if (filterContext == null)
        
            throw new ArgumentNullException("filterContext");
        

        if (AuthorizeCore(filterContext.HttpContext))
        
            SetCachePolicy(filterContext);
        
        else if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
        
            // auth failed, redirect to login page
            filterContext.Result = new HttpUnauthorizedResult();
        
        else if (filterContext.HttpContext.User.IsInRole("SuperUser"))
        
            // is authenticated and is in the SuperUser role
            SetCachePolicy(filterContext);
        
        else
        
            ViewDataDictionary viewData = new ViewDataDictionary();
            viewData.Add("Message", "You do not have sufficient privileges for this operation.");
            filterContext.Result = new ViewResult  MasterName = this.MasterName, ViewName = this.ViewName, ViewData = viewData ;
        
    

    protected void SetCachePolicy(AuthorizationContext filterContext)
    
        // ** IMPORTANT **
        // Since we're performing authorization 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 a page should be served from the cache.
        HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
        cachePolicy.SetProxyMaxAge(new TimeSpan(0));
        cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
    

现在我们可以使用我们自己的属性来重定向我们的用户访问被拒绝的视图:

[MyAuthorization(Roles = "Portal Manager,Content Editor", ViewName = "AccessDenied")]
public class DropboxController : Controller
 
    .......

就是这样! 超级骗子!

以下是我用来获取所有这些信息的一些链接:

自定义角色提供者: http://davidhayden.com/blog/dave/archive/2007/10/17/CreateCustomRoleProviderASPNETRolePermissionsSecurity.aspx

希望这些信息对您有所帮助!

【讨论】:

你解释这件事的方式太耸人听闻了!!我敢打赌你甚至没有那么努力......你应该考虑写博客文章:)。 谢谢哥们,很高兴它有帮助。我发现自己经常这样做,通过这样做我自己更了解它:-) 什么是“new MyUserProvider();”和第一个代码sn-p中的“CurrentTerritoryID”是什么意思?这看起来很简单,所以我希望这能完成工作:) 谢谢! 嗨,基本上 ValidateUser 方法是您应该针对您的数据库验证您的用户凭据的地方。我做了一个额外的类,所以我可以发送更多参数(在这种情况下,它是我用作 MyMembershipProvider 类之一的 CurrentTerritoryID 参数)。尽管如此,您可以选择只是在那里验证它或通过另一个层/类/方法 那是因为 MembershipProvider 是一个带有抽象方法的抽象类,你必须在你的类中实现这些抽象方法。 (如果你愿意,这些方法可以是空的并且什么也不做)。关于 ASPNETDB 文件,它是微软成员模式的默认文件。您可以要求您的会员提供者在您的数据库服务器上使用相同的模式而不是文件(使用连接字符串),或者像我们在这里所做的那样使用自定义会员提供者使用您自己的表...【参考方案2】:

这对我有用 http://mattwrock.com/post/2009/10/14/Implementing-custom-Membership-Provider-and-Role-Provider-for-Authinticating-ASPNET-MVC-Applications.aspx

【讨论】:

您能否总结一下链接中回答用户问题并提供链接的要点? 相同的 Web 存档链接 - here【参考方案3】:

也可以用更少量的代码来使用它,我不完全确定这种方法是否同样安全,但可以很好地与您使用的任何数据库一起使用。

在 global.asax 中

protected void Application_AuthenticateRequest(object sender, EventArgs e)
    
        if (HttpContext.Current.User != null)
        
            if (HttpContext.Current.User.Identity.IsAuthenticated)
            
                if (HttpContext.Current.User.Identity is FormsIdentity)
                
                    FormsIdentity id =
                        (FormsIdentity)HttpContext.Current.User.Identity;
                    FormsAuthenticationTicket ticket = id.Ticket;

                    // Get the stored user-data, in this case, our roles
                    string userData = ticket.UserData;
                    string[] roles = userData.Split(',');
                    HttpContext.Current.User = new GenericPrincipal(id, roles);
                
            
        
    

这样做是它从由 FormsAuthenticationTicket 制作的 authCookie 中读取角色

登录逻辑如下所示

public class dbService

    private databaseDataContext db = new databaseDataContext();

    public IQueryable<vwPostsInfo> AllPostsAndDetails()
    
        return db.vwPostsInfos;
    

    public IQueryable<role> GetUserRoles(int userID)
    
        return (from r in db.roles
                    join ur in db.UsersRoles on r.rolesID equals ur.rolesID
                    where ur.userID == userID
                    select r);
    

    public IEnumerable<user> GetUserId(string userName)
    
        return db.users.Where(u => u.username.ToLower() == userName.ToLower());
    

    public bool logOn(string username, string password)
    
        try
        
            var userID = GetUserId(username);
            var rolesIQueryable = GetUserRoles(Convert.ToInt32(userID.Select(x => x.userID).Single()));
            string roles = "";
            foreach (var role in rolesIQueryable)
            
                roles += role.rolesName + ",";
            

            roles.Substring(0, roles.Length - 2);
            FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
                       1, // Ticket version
                       username, // Username associated with ticket
                       DateTime.Now, // Date/time issued
                       DateTime.Now.AddMinutes(30), // Date/time to expire
                       true, // "true" for a persistent user cookie
                       roles, // User-data, in this case the roles
                       FormsAuthentication.FormsCookiePath);// Path cookie valid for

            // Encrypt the cookie using the machine key for secure transport
            string hash = FormsAuthentication.Encrypt(ticket);
            HttpCookie cookie = new HttpCookie(
               FormsAuthentication.FormsCookieName, // Name of auth cookie
               hash); // Hashed ticket

            // Set the cookie's expiration time to the tickets expiration time
            if (ticket.IsPersistent) cookie.Expires = ticket.Expiration;

            // Add the cookie to the list for outgoing response
            HttpContext.Current.Response.Cookies.Add(cookie);

            return true;
        
        catch
        
            return (false);
        
    

我使用两个表将角色存储在我的数据库中:表:角色,其中包含列:roleID 和 roleName,表:UsersRoles 包含列:userID 和 roleID,这使得多个用户的多个角色成为可能,并且很容易创建自己的逻辑来添加/删除用户的角色等等。例如,这使您可以使用 [Authorize(Roles="Super Admin")]。希望这会有所帮助。

编辑:忘记进行密码检查,但您只需在 logOn 方法中添加一个 if 来检查提供的用户名和密码是否检查,如果不检查则返回 false

【讨论】:

等等,所以您将角色名称存储在 auth cookie 中?这是否意味着用户可以在他们的 auth cookie 中放置他们想要的任何角色?我想这没关系,因为他们必须解密 cookie? @Pandincus:是的,如果用户设法解密 cookie,这是使用此方法的缺点之一,可以做的是进一步加密角色并提供公钥以及cookie 供以后在 global.asax 中解密。它并不完美,但它可以完成工作并且没有那么复杂。【参考方案4】:

我使用 NauckIt.PostgreSQL 提供程序的源代码作为基础,并对其进行了修改以满足我的需要。

【讨论】:

以上是关于如何为 ASP.NET MVC 2 创建自定义成员资格提供程序?的主要内容,如果未能解决你的问题,请参考以下文章

如何为 Asp.NET MVC 5 创建 ClaimsIdentity 对象?

ASP.Net MVC 2 中自定义成员资格提供程序中的角色分组

ASP.NET MVC:在不影响性能的情况下路由自定义 slug

如何为 ASP.NET MVC 5 应用程序设置时区?

如何为 ASP.NET MVC 处理“未找到 404 页面”查询的全部路由?

asp.net MVC 如何为一个视图中某个表单(Form)指定某个提交方法(如CreateRole)