Авторизация ASP.NET MVC

Почему не следует использовать MembershipProvider?

Google подробно ответит на этот вопрос. Приведу несколько причин, почему я не люблю использовать этот провайдер:

Самое простое решение

Для того, чтобы реализовать авторизацию необходимо написать всего 1 строчку кода:

FormsAuthentication.SetAuthCookie(user.Id, createPersistentCookie: true);

Успех! На этом можно закончить, если требуется простейшая авторизация пользователя в ASP.NET приложении. Код, что приведен выше записывает Cookie в ответ сервера в текущий HttpContext. При следующем запросе пользователь передаст серверу установленную ранее Cookie, чтобы сервер проверил авторизован пользователь или нет.

Расширенное решение

Для начала необходимо описать интефейс нашей системы авторизации

 public interface IAuthenticationService
    {

    <span class="keyword">void</span> Login(User user, <span class="keyword">bool</span> rememberMe);

    <span class="keyword">void</span> Logoff();

    <span class="built_in">string</span> GeneratePassword(<span class="built_in">string</span> pass, <span class="built_in">string</span> salt);

    User CurrentUser { get; }

}</pre>

Начнем реализовывать методы поочередно 

public class FormsAuthenticationService : IAuthenticationService
    {
        private const string AuthCookieName = “AuthCookie”;

    private IAccountRepository _accountRepository;

    public FormsAuthenticationService(IAccountRepository accountRepository)
    {
        _accountRepository = accountRepository;
    }


    public void Login(User user, bool rememberMe)
    {
        DateTime expiresDate = DateTime.Now.AddMinutes(30);
        if (rememberMe)
            expiresDate = expiresDate.AddDays(10);


        FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
            1,
            user.ID.ToString(),
            DateTime.Now,
            expiresDate, rememberMe,user.ID.ToString());
        string encryptedTicket = FormsAuthentication.Encrypt(ticket);

        SetValue(AuthCookieName, encryptedTicket, expiresDate);


        _currentUser = user;
    }

    public void Logoff()
    {
        SetValue(AuthCookieName, null, DateTime.Now.AddYears(-1));
        _currentUser = null;
    }

    /// &lt;summary&gt;
    /// Generate password
    /// &lt;/summary&gt;
    /// &lt;param name="pass"&gt;Original password&lt;/param&gt;
    /// &lt;param name="salt"&gt;User ID + " " + User.ID&lt;/param&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    public string GeneratePassword(string pass, string salt)
    {
        return OxoCrypt.MD5(pass + OxoCrypt.MD5(pass + salt + " " + salt));
    }

    private User _currentUser;
    public User CurrentUser
    {
        get
        {
            if (_currentUser == null)
            {
                try
                {
                    object cookie = HttpContext.Current.Request.Cookies[AuthCookieName] != null ? HttpContext.Current.Request.Cookies[AuthCookieName].Value : null;
                    if (cookie != null &amp;&amp; !string.IsNullOrEmpty(cookie.ToString()))
                    {
                        var ticket = FormsAuthentication.Decrypt(cookie.ToString());
                        _currentUser =_accountRepository.GetUserByID(ticket.Name.ToInteger(0));
                    }

                }
                catch (Exception ex)
                {
                    _currentUser = null;
                }
            }
            return _currentUser;
        }
    }<br>
    public static void SetValue(string cookieName, string cookieObject, DateTime dateStoreTo)
    {         

        HttpCookie cookie = HttpContext.Current.Response.Cookies[cookieName];
        if (cookie == null)
        {
            cookie = new HttpCookie(cookieName);
            cookie.Path = "/";
        }

        cookie.Value = cookieObject;
        cookie.Expires = dateStoreTo;

        HttpContext.Current.Response.SetCookie(cookie);
    }
}</pre>

На этом моменте реализован класс, которые отвечает за обработку и хранение авторизационных данных. Осталось реализовать защиту и проверку пользователя на роль. Стандартный пример авторизации ASP.NET MVC предлагает использовать проверку роли как атрибут с текстовым значением роли. Идея атрибута мне нравится, однако текстом писать каджый раз роль - определенно нет. Могут возникнуть ошибки написания и сложно будет изменить роли. 

Enum для определения ролей

 public enum UserRoles
    {
        None = 0,

    Admin = <span class="number">1</span>,

    Manager = <span class="number">2,<br><br>        Client = 3<br></span>
}

<span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> RolesHelper
{
    <span class="keyword">private</span> <span class="keyword">static</span> UserRoles[] _roles = <span class="keyword">null</span>;
    <span class="keyword">public</span> <span class="keyword">static</span> UserRoles[] GetRoles()
    {
        <span class="keyword">if</span> (_roles == <span class="keyword">null</span> || _roles.Length == <span class="number">0</span>)
        {
            UserRoles[] roles = Enum.GetValues(typeof(UserRoles)) <span class="keyword">as</span> UserRoles[];
            <span class="keyword">List</span>&lt;UserRoles&gt; rolesToArr = <span class="keyword">new</span> <span class="keyword">List</span>&lt;UserRoles&gt;();
            <span class="keyword">foreach</span> (UserRoles userRolese in roles)
            {
                <span class="keyword">if</span> (userRolese != UserRoles.None)
                {
                    rolesToArr.Add(userRolese);
                }
            }

            _roles = rolesToArr.ToArray();
        }

        <span class="keyword">return</span> _roles;

    }
}</pre>

Выше описано перечисления всех ролей возможных в системе и определен класс Helper для удобной работы с ролями.

Атрибут авторизации

Для создания своего атрибута авторизации необходимо наследовать его от AuthorizeAttribute. При этом мы реализуем проверку ролей по нашему сценарию. Здесь добавлено свойство AllowAnonymus, которое позволяет получить доступ анонимному пользователю (если мы повесили атрибут на контроллер, например на AccountController необходимо указать атрибут, но на Action Login - необходимо дать доступ неавторизованному пользователю.

 public class AuthenticateAttribute : AuthorizeAttribute
    {
        public bool AllowAnonymus { get; set; }

    <span class="keyword">public</span> UserRoles AccessTole { get; <span class="built_in">set</span>; }

    <span class="keyword">public</span> AuthenticateAttribute()
    {
    }

    <span class="keyword">public</span> AuthenticateAttribute(<span class="keyword">bool</span> allowAnonymus)
    {
        AllowAnonymus = allowAnonymus;
    }

    <span class="keyword">public</span> AuthenticateAttribute(UserRoles accessRole) { AccessTole = accessRole; }

    <span class="keyword">protected</span> override <span class="keyword">bool</span> AuthorizeCore(System.Web.HttpContextBase httpContext)
    {
        <span class="keyword">if</span> (AllowAnonymus)
            <span class="keyword">return</span> <span class="keyword">true</span>;


        User user = DependencyResolver.Current.GetService&lt;IAuthenticationService&gt;().CurrentUser;
        <span class="keyword">if</span> (user == null)
            <span class="keyword">return</span> <span class="keyword">false</span>;

        <span class="keyword">if</span> (AccessTole == <span class="number">0</span>)
            <span class="keyword">return</span> <span class="keyword">true</span>;

        <span class="keyword">return</span> user.IsInRole(AccessTole);
    }

    <span class="keyword">protected</span> override <span class="keyword">void</span> HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
    {
        filterContext.Result = <span class="keyword">new</span> System.Web.Mvc.RedirectResult(<span class="string">"/login"</span>, <span class="keyword">false</span>);          
    }

}</pre>

Controller MVC

Создаем базовый контроллер. 

 public abstract class ControllerBase : Controller
    {
        protected IAuthenticationService _AuthenticationService;

    public ControllerBase()
    {
        _AuthenticationService = DependencyResolver.Current.GetService&lt;IAuthenticationService&gt;();
    }

    public User CurrentUser
    {
        get
        {
            return _AuthenticationService.CurrentUser;
        }
    }

    protected bool IsAuthenticated
    {
        get { return CurrentUser != null; }
    }
}</pre>

BaseViewPage и WebViewPage

Для удобства также создадим базовые версии для страниц (Razor) 

 public class BaseViewPage : WebViewPage
    {
        public virtual new User User
        {
            get
            {
                return DependencyResolver.Current.GetService<IAuthenticationService>().CurrentUser;
            }
        }

    <span class="keyword">public</span> <span class="keyword">bool</span> IsAuthenticated
    {
        get
        {
            <span class="keyword">return</span> User != null;
        }
    }

    <span class="keyword">public</span> <span class="keyword">bool</span> IsInRole(UserRoles roles)
    {
        <span class="keyword">if</span> (User == null)
            <span class="keyword">return</span> <span class="keyword">false</span>;

        <span class="keyword">return</span> User.IsInRole(roles);
    }

    <span class="keyword">public</span> override <span class="keyword">void</span> Execute() { <span class="keyword">throw</span> <span class="keyword">new</span> NotImplementedException(); }
}

<span class="keyword">public</span> <span class="keyword">class</span> BaseViewPage&lt;TModel&gt; : WebViewPage&lt;TModel&gt;
{
    <span class="keyword">public</span> <span class="keyword">virtual</span> <span class="keyword">new</span> User User
    {
        get
        {
            <span class="keyword">return</span> DependencyResolver.Current.GetService&lt;IAuthenticationService&gt;().CurrentUser;
        }
    }

    <span class="keyword">public</span> <span class="keyword">bool</span> IsAuthenticated
    {
        get
        {
            <span class="keyword">return</span> User != null;
        }
    }

    <span class="keyword">public</span> <span class="keyword">bool</span> IsInRole(UserRoles roles)
    {
        <span class="keyword">if</span> (User == null)
            <span class="keyword">return</span> <span class="keyword">false</span>;

        <span class="keyword">return</span> User.IsInRole(roles);
    }

    <span class="keyword">public</span> override <span class="keyword">void</span> Execute() { <span class="keyword">throw</span> <span class="keyword">new</span> NotImplementedException(); }
}</pre>

После создания базовых страниц, необходимо их объявить здесь: ~/Views/Web.config

<pages pageBaseType=OxoBlog.Web.Application.Mvc.BaseViewPage>
      <namespaces>
        <add namespace=“System.Web.Mvc” />
        <add namespace=“System.Web.Mvc.Ajax” />
        <add namespace=“System.Web.Mvc.Html” />
        <add namespace=“System.Web.Routing” />
        <add namespace=“OxoBlog.Web.ViewModels”/>
        <add namespace=“OxoBlog.Web.Application.Mvc”/>
      </namespaces>
 </pages>

Кроме того, сюда сразу стоит добавить пространства имен которые вы планируете использовать, чтобы движок ASP.NET MVC сразу понял где стоит искать методы, используемые в представлениях.

Ссылки

Комментарии

comments powered by Disqus