Как создать настраиваемого поставщика членства для ASP.NET MVC 2?

Как создать настраиваемое членство для ASP.NET MVC 2 на основе поставщика членства ASP.NET?


person ilija veselica    schedule 05.05.2010    source источник


Ответы (4)


Я создал новый проект, содержащий настраиваемого поставщика членства, и переопределил метод ValidateUser из абстрактного класса MembershipProvider:

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);
    }
}

Затем я подключил этого поставщика к моему проекту ASP.NET MVC 2, добавив ссылку и указав ее в моем web.config:

<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 использует этот метод, чтобы узнать, какие роли назначены текущему вошедшему в систему пользователю, и убедиться, что пользователю разрешен доступ к действию контроллера.

Вот шаги, которые нам нужно предпринять:

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) Подключите поставщика ролей к приложению ASP.NET MVC 2 через наш web.config:

<system.web>
...

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

...
</system.web>

3) Установите авторизацию (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 */);
    }
}

Теперь мы можем использовать наш собственный атрибут made для перенаправления наших пользователей для доступа к запрещенному просмотру:

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

Вот и все! Супер-пупер!

Вот некоторые из ссылок, которые я использовал, чтобы получить всю эту информацию:

Пользовательский поставщик ролей: http://davidhayden.com/blog/dave/archive/2007/10/17/CreateCustomRoleProviderASPNETRolePermissionsSecurity.aspx

Я надеюсь эта информация поможет!

person danfromisrael    schedule 06.05.2010
comment
то, как вы объяснили это, сенсационно !! и я уверен, что вы даже не так сильно старались ... вам стоит подумать о том, чтобы писать сообщения в блоге :). - person Erx_VB.NExT.Coder; 06.05.2010
comment
то, как вы объяснили это, сенсационно !! и я уверен, что вы даже не так сильно старались ... вам стоит подумать о том, чтобы писать сообщения в блоге :). - person Erx_VB.NExT.Coder; 06.05.2010
comment
спасибо, приятель, рад, что помог. Я часто так делаю, и, делая это, я лучше понимаю это :-) - person danfromisrael; 06.05.2010
comment
Что делает new MyUserProvider (); а CurrentTerritoryID в первом фрагменте кода имеют в виду? Это выглядит довольно просто, так что я надеюсь, что это сработает :) Спасибо! - person ilija veselica; 07.05.2010
comment
привет, в основном метод ValidateUser - это то место, где вы должны проверить свои учетные данные пользователя в вашей базе данных. Я создал дополнительный класс, чтобы я мог отправлять больше параметров (в данном случае это параметр CurrentTerritoryID, который я использовал как один из классов MyMembershipProvider). тем не менее, вы можете просто проверить его там или пройти через другой уровень / класс / метод - person danfromisrael; 09.05.2010
comment
хм ... я не совсем понимаю ... есть ли у вас какой-нибудь пример кода? С помощью этого настраиваемого поставщика я смогу использовать свои собственные таблицы пользователей и ролей? А как насчет существующих таблиц в ASPNETDB? Извините, если я спросил что-то бессмысленное, но я новичок в ASP.NET ... спасибо! - person ilija veselica; 09.05.2010
comment
Кстати, класс MyMembershipProvider не завершен? Когда я компилирую его, я получаю кучу ошибок: MyMembershipProvider 'не реализует унаследованный абстрактный член' System.Web.Security.MembershipProvider.FindUsersByEmail (string, int, int, out int) '.... Я полагаю, в нем должно быть все эти методы: erictopia.com/2010/05/ - person ilija veselica; 10.05.2010
comment
это потому, что MembershipProvider - это абстрактный класс с абстрактными методами, которые вы должны реализовать в своем классе. (эти методы могут быть пустыми и ничего не делать, если хотите). Что касается файла ASPNETDB, это файл по умолчанию для схемы членства Microsoft. вы можете попросить своего поставщика членства использовать ту же схему на вашем сервере БД вместо файла (используя строку подключения) или, в качестве альтернативы, использовать свои собственные таблицы с помощью настраиваемого поставщика членства, как мы это сделали здесь ... - person danfromisrael; 10.05.2010
comment
Мне удалось это настроить. Я использовал комбинацию вашего сообщения и сообщения из ссылки uriDium: mattwrock.com/post/2009/10/14/. Ваш пост не завершен и меня это немного смутило. Важно, чтобы все методы были в коде, но те, которые не используются, должны быть определены как Не реализованные - это шаг, который доставил мне много проблем. Спасибо :) - person ilija veselica; 18.05.2010
comment
Хороший момент, я использовал автозаполнение Visual Studio для наследования абстрактных классов. то, что он делает, автоматически реализует все абстрактные методы для вас как не реализованные, а затем вы можете выбрать, в какой из них вы хотите ввести свой собственный код ... - person danfromisrael; 22.05.2010
comment
Как именно вы использовали автозаполнение Visual Studio для наследования абстрактных классов? Вы подразумеваете, что Visual Studio распознала, какие методы отсутствуют, и автоматически реализовала их как не реализованные? - person ilija veselica; 26.05.2010
comment
нет, я имею в виду, что он просто пишет это для вас. попробуйте: просто начните вводить: public class blablaMembership: MembershipProvider, и вы увидите небольшое синее подчеркивание под буквой M MembershipProvider. щелкните его, и у вас будет возможность реализовать абстрактный класс «MembershipProvider». Как только вы щелкнете по нему, он запишет все методы MembershipProvider с реализацией: throw new NotImplementedException (); - person danfromisrael; 26.05.2010
comment
то, как вы объяснили это, сенсационно !! и я уверен, что вы даже не так сильно старались ... вам стоит подумать о том, чтобы писать сообщения в блоге :). - person kenwarner; 25.05.2011
comment
Вы можете объяснить или показать мне MyUserProvider () и CurrentTerritoryID? Спасибо. - person Nothing; 24.11.2011
comment
Смотрите объяснение в комментариях. это просто класс, который проверяет ваши данные на соответствие БД, и вы можете отправить ему любые параметры, которые захотите. в этом случае я отправил CurrentTerritoryID, но вы можете создать любой класс с любыми свойствами, которые вам нужны - person danfromisrael; 30.11.2011


Также можно использовать это с гораздо меньшим объемом кода, я не совсем уверен, что этот метод так же безопасен, но очень хорошо работает с любой базой данных, которую вы используете.

в 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);
                }
            }
        }
    }

что он делает, так это то, что он читает роли из authCookie, который был создан из FormsAuthenticationTicket

и логика входа выглядит так

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")]. надеюсь это поможет.

изменить: забыл проверить пароль, но вы просто добавляете if в метод logOn, который проверяет, проверяются ли предоставленные имя пользователя и пароль, и если нет, он возвращает false

person Joakim    schedule 27.05.2010
comment
Подождите, так вы сохраняете имена ролей в cookie аутентификации? Разве это не означает, что пользователь может помещать любые роли в свой файл cookie аутентификации? Думаю, это не имеет значения, ведь им придется расшифровать куки? - person Pandincus; 27.02.2011
comment
@Pandincus: Да, это один из недостатков использования этого метода, если пользователю удается расшифровать файл cookie, что можно сделать, это дополнительно зашифровать роли и предоставить открытый ключ вместе с файлом cookie для последующего дешифрования в глобальном масштабе. asax. Он не идеален, но выполняет свою работу и не так уж и сложен. - person Joakim; 28.02.2011

Я использовал исходный код провайдера NauckIt.PostgreSQL в качестве основы и модифицировал его в соответствии со своими потребностями.

person SztupY    schedule 05.05.2010