7 квітня 2015 р.

Автентифікація и авторизація в ASP.NET mvc. Свій authorize attribute

Авторизація та Автентифікáція виконують дуже важливі функції на сайті і навряд якийсь серйозний веб-сервіс обійдеться без них. Давайте детальніше розглянемо ці два поняття. 

 

Авторизація - керування рівнями та засобами доступу до певного захищеного ресурсу та ресурсів системи залежно від ідентифікатора і пароля користувача або надання певних повноважень (особі, програмі) на виконання деяких дій у системі обробки даних.


  Простіше кажучи, авторизація це процес який вирішує чи давати користувачу доступ до чогось (захищена сторінка, чужа переписка і тд.) 


Автентифікàція (англ. Authentication) — це перевірка достовірності пред'явленого користувачем ідентифікатора. Позитивним результатом автентифікації є авторизація користувача, тобто надання йому прав доступу до ресурсів, визначених для виконання його завдань. Залежно від важливості ресурсу, для доступу до нього можуть застосовуватися різні методи автентифікації.

 

Я не буду вдаватися в теорію, вважаю, що це не є аж так потрібно, тому перейдемо до практики.


На практиці я використовую свій authorize attribute. Навіщо це потрібно? – Ну першою причиною є те, що стандартний [Authorize] деколи викидав моїх користувачів, з незрозумілих причин, причому працювало все гаразд тільки на localhost. Другою причиною є те, що стандартний набір ролі-користувачі складно інтегрується із своєю базою даних, що не дуже радувало мене та інших програмістів asp.net. 


Логіка програми проста, спочатку користувач вводить свої дані, система шукає користувача за ними, якщо його немає повертає помилку вводу логіна чи пароля. Якщо ж користувач з такими даними є, то в cookies робиться який запис, щоб надалі зчитувати його і давати користувачу певний доступ. Я зазвичай шифрую email, це не дуже безпечно і в більш серйозних проектах це великий прокол в безпеці, але для малих сайті, до яких хакерам, та іншим зловмисникам не буде ніякого інтересу цілком допустимий шлях, але це тільки моя думка.


І так, що має робити наш атрибут? – він повинен зчитати cookies, розшифрувати інформацію, та дати або не дати користувачу доступ за його запитом, це вже залежить від ролі(рівня доступу) самого користувача. 


Структура бази даних

Рис 1. Структура БД користувачі і ролі.


База даних (далі БД) містить 3 таблиці User (Користувач) містить дані користувача, Role(Роль) містить список ролей та UserRole це стикувальна таблиця, яка містить привязки користувача до ролей.


  Структура передбачає, що один користувач може мати кілька ролей одночасно(тобто рівнів доступу)

 

Логіка програми

 Код нашого атрибута: 

    public class AuthenticateAttribute : AuthorizeAttribute

    {

        DataManager dm = new DataManager();

        public bool AllowAnonymus { get; set; }

 

        public AuthenticateAttribute() { }

 

        protected override bool AuthorizeCore(HttpContextBase httpContext)

        {

            if (AllowAnonymus)

                return true;

            User user = FormsAuthenticationService.User;

            if (user != null)

                if (base.Roles == null)

                    return true;

                else

                    return user.IsInRole(base.Roles);

            return false;

        }

 

        protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)

        {

            filterContext.Result = new System.Web.Mvc.RedirectResult("/Account/Login/?ReturnUrl=" + filterContext.HttpContext.Request.Url.AbsolutePath);

        }

    }

 

Для того, щоб створити власний атрибут необхідно створити клас, який буде наслідуватися від стандартного AuthorizeAttribute.  Далі перевантажимо функцію  AuthorizeCore, яка буде працювати наступним чином: якщо значення поля AllowAnonymus true, тоді ми даємо доступ (повертаємо true для функції AuthorizeCore).  Далі якщо нічого немає на вході створимо об`єкт користувача (саме для користувача ми ставимо права доступу). І так, якщо user null, значить що користувач не ввійшов до системи тоді, повертаємо false, якщо ж ні, то треба перевірити чи задали ми ролі,  якщо ні, то повертаємо true, і даємо користувачу доступ, так як більше ніяких обмежень немає. Якщо ролі є то треба повернути значення true або false в залежності чи має користувач необхідну для доступу роль чи ні це робить функція IsInRole() 


  HandleUnauthorizedRequest це перевантажена функція означає реакцію системи, на запит авторизації. Тобто, коли неавторизований користувач намагається отримати доступ, виконається дана функція та перенаправить його на таку сторінку

/Account/Login/?ReturnUrl=" + filterContext.HttpContext.Request.Url.AbsolutePath,

  де filterContext.HttpContext.Request.Url.AbsolutePath – це сторінка до якої намагався отримати доступ користувач. Її треба передати, щоб після авторизації користувача перенаправити його на неї вже як авторизованого.


  Розглянемо наступний клас в якому відбуваються перевірка на роль, та зчитування даних користувача.

   public class FormsAuthenticationService

    {

        public const string AuthCookieName = "_data";   //назва кукі

 

        public static void Login(User user)

        {

            DateTime expiresDate = DateTime.Now.AddMinutes(30);

            if (rememberMe)

                expiresDate = expiresDate.AddMonths(1);

            SetValue(AuthCookieName, user.Email, expiresDate);

        }

 

        public static User User        

         {

            get

            {

                DataManager dm = new DataManager();

                User user = null;

                object cookie = HttpContext.Current.Request.Cookies[AuthCookieName] != null ? HttpContext.Current.Request.Cookies[AuthCookieName].Value : null;

                if (cookie != null && !string.IsNullOrEmpty(cookie.ToString()))

                {

                    try

                    {

                        //розшифровуємо кукі

                        string email = StringCipher.Decrypt(cookie.ToString());

                        user = dm.GetUsers().FirstOrDefault(u => u.Email == email);

                    }

                    catch

                    {

                        return user;

                    }

                }

                return user;

            }

        }

 

        public static void Logout() //виходимо

        {

            var cookies = new HttpCookie(AuthCookieName);

            cookies.Value = "Slava Ukraini";

            cookies.Expires = DateTime.Now.AddMonths(1);

            HttpContext.Current.Response.Cookies.Add(cookies);

 

        }

        //ставимо кукі шуфруючи StringCipher.Encrypt(cookieObject)

        private static void SetValue(string cookieName, string cookieObject, DateTime dateStoreTo)

        {

            HttpCookie userCookie = new HttpCookie(AuthCookieName, StringCipher.Encrypt(cookieObject));

            userCookie.Expires = dateStoreTo;

            HttpContext.Current.Response.SetCookie(userCookie);

        }

 

Логінування виконується за допомогою функції Login, вона приймає значення обєкта користувача, який логується. Для виходу використовується функція Logout(). Ці дві функції використовують функцію SetValue() яка просто записує дані cookies. При вході вона записує email користувача, в зашифрованому вигляді (це не є дуже безпечний шлях, але я так зробив для прикладу, на Ваших сайтах я рекомендую зробити, щось більш безпечніше, наприклад хешувати і логін і пароль, а тоді записувати в cookies). А при виході замість значення email користувача записуємо надпис Slava Ukraini за яким, не можна буде знайти користувача...


  Основною особливістю даного класу є поле User. Воно повертає об`єкт користувача який залогінений. Для цього я розшифровую cookies, та шукаю потрібного користувача, в свої БД (DataManager це клас з набором функцій для роботи з БД). Коли користувач є, то функція повертає його об`єкт, в противному випадку null (як ви пам`ятаєте так відбувається перевірка, якщо дане поле поверне значення, то користувач вводив дані, і вони записалися в cookies, значить можна дати йому доступ. див. вище).


  Як бачите все дуже просто. Це елементарна логіка, яку я використовую. У ваших сервісах я рекомендую написати, щось більш правильне з точки зору безпеки… Тут, наприклад, я повертаю обєкт користувача тільки за адресом пошти, хоч він і зашифрований це все одно не достатьо. Будь хто може зашифрувати в cookies чийсь email взламавши алгоритм і він отримає доступ до сайту без проблем. Тому добре продумайте ваш алгоритм перевірки!


  Ще одна функція про яку я хочу розповісти це IsInRole. Вона приймає параметром стрічку, яка містить в собі список Ролей користувачів яким необхідно дати доступ. Ролі можна записувати через кому, крапку, пробіл, і навіть разом.

 

        public bool IsInRole(string Roles)         

  {

            foreach (var r in FormsAuthenticationService.User.Roles)

                if (Roles.Contains(r.Code))

                    return true;

            return false;

        }

 

FormsAuthenticationService.User.Roles це клас згенерований entity framework для таблиці UserRole нашої БД, він повертає список ролей для залогінованого користувача. Як бачите для кожної ролі в цьому листі, я перевіряю наявності її кодового імені, в стрічці параметрі Roles. Саме тому, як буде записана назва ролей немає значення, головне вказати правильне кодове імя. При будь якому співпадіння функція повертає true і це означає, що доступ отримано. В іншому випадку false. Це означає, що даний користувач не має необхідної ролі, тому доступ до ресурсу йому давати не можна.

 

Подивимося як це все виглядає


        public ActionResult Login(LoginViewModel login, string ReturnUrl)

        {

            if (!ModelState.IsValid)

                ModelState.AddModelError("", "Введені дані не вірні");

            else

            {

                var user = dm.GetUsers().FirstOrDefault(u => u.Email == login.EmailLogin && u.Password == login.PasswordLogin);

                if (user != null

                {

                    FormsAuthenticationService.Login(user, login.RememberMe);

                    string decodedUrl = "";

                    if (!string.IsNullOrEmpty(ReturnUrl))

                        decodedUrl = Server.UrlDecode(ReturnUrl);

                    if (Url.IsLocalUrl(decodedUrl))

                        return Redirect(decodedUrl);

                    else

                        return RedirectToAction("UserPage");

                }

                else

                    ModelState.AddModelError("", "Не вірний логін чи пароль!");

            }

            return PartialView("Index");

        }

 

Це ActionResult Login, з AccountController він приймає LoginViewModel, це модель, що містить логін і пароль, які вводить користувач. Тут перевіряється валідність моделі та інше.. Я створюю обєк користувача user, і просто шукаю його б БД за логіном і паролем (зверніть увагу вони в мене не хешовані і не шифровані. Дбайте про свою безпеку це просто приклад). Коли такий обєкт є то функція поверне його, далі ми запишемо дані в кукі за допомогою функції FormsAuthenticationService.Login(user); після цього зчитуємо ReturnUrl, на який треба перейти після авторизації, в іншому пипадку я перенаправляю користувача на головну сторінку.


 Приклад використання атрибутів

 

    [AuthenticateAttribute]

    public class HomeController : Controller

    {

        [AuthenticateAttribute(allowAnonymus:true)]

        public ActionResult Index()

        {

            return View();

        }

 

        [AuthenticateAttribute(roles = "admins-moderator/customer.simplejustuser")]

        public ActionResult auto()

        {

            return View();

        } 


Доступ до контролера мають тільки авторизовані користувачі. Про те до ActionResult Index мають доступ усі (allowAnonymus:true)... Доступ до ActionResult auto будуть мати користувачі, ролі яких будуть наступні (admin, moderator, customer, simple, justuser) як бачите порядок запису немає знчачення, чи відступи, чи щось інше, це повязано з виконанням фунцікці IsInRole (див вище).

Це все, якщо у вас виникли запитання залишайте їх тут. Дякую.

 

     9625       Автор - Богдан Ярчак

Залиште коментар


Коментарі

 JimmiNu          11.09.2017 6:30:46

k8U7rL http://www.FyLitCl7Pf7ojQdDUOLQOuaxTXbj5iNG.com

 JimmiNu          10.09.2017 3:18:36

xaWMY9 http://www.FyLitCl7Pf7ojQdDUOLQOuaxTXbj5iNG.com

 JimmiNu          09.09.2017 0:42:01

lyg8r6 http://www.FyLitCl7Pf7ojQdDUOLQOuaxTXbj5iNG.com

 Barnypok          09.07.2017 4:10:05

38P0Fb http://www.LnAJ7K8QSpkiStk3sLL0hQP6MO2wQ8gO.com

 Barnypok          08.07.2017 23:34:09

7zC07u http://www.LnAJ7K8QSpkiStk3sLL0hQP6MO2wQ8gO.com

 Barnypok          07.07.2017 17:22:11

DLXN2W http://www.LnAJ7K8QSpkiStk3sLL0hQP6MO2wQ8gO.com

 JimmiXzSq          20.05.2017 2:30:25

2tYhli http://www.LnAJ7K8QSpkiStk3sLL0hQP6MO2wQ8gO.com

 JimmiXzSq          19.05.2017 7:05:11

xD1LQt http://www.LnAJ7K8QSpkiStk3sLL0hQP6MO2wQ8gO.com

 JimmiXzSq          18.05.2017 8:31:31

cGjJtm http://www.LnAJ7K8QSpkiStk3sLL0hQP6MO2wQ8gO.com

 Barnypok          01.04.2017 23:03:27

95R3CJ http://www.LnAJ7K8QSpkiStk3sLL0hQP6MO2wQ8gO.com

 Barnypok          01.04.2017 3:23:08

LxQpap http://www.LnAJ7K8QSpkiStk3sLL0hQP6MO2wQ8gO.com

 matt          20.02.2017 15:25:03

E9s8vV http://www.y7YwKx7Pm6OnyJvolbcwrWdoEnRF29pb.com

 matt          20.02.2017 13:29:02

UB7dJJ http://www.y7YwKx7Pm6OnyJvolbcwrWdoEnRF29pb.com

 gordon          20.02.2017 11:32:33

2NnYzT http://www.y7YwKx7Pm6OnyJvolbcwrWdoEnRF29pb.com

 chaba          20.02.2017 9:35:31

zA8MuB http://www.y7YwKx7Pm6OnyJvolbcwrWdoEnRF29pb.com

 gordon          20.02.2017 7:39:59

zfgHwH http://www.y7YwKx7Pm6OnyJvolbcwrWdoEnRF29pb.com

 JimmiXzSw          19.02.2017 5:34:22

lIOQRH http://www.FyLitCl7Pf7ojQdDUOLQOuaxTXbj5iNG.com

 gordon          31.01.2017 21:36:10

yQWAVK http://www.y7YwKx7Pm6OnyJvolbcwrWdoEnRF29pb.com

 gordon          31.01.2017 18:30:11

RT2roA http://www.y7YwKx7Pm6OnyJvolbcwrWdoEnRF29pb.com

 matt          29.01.2017 19:37:42

u9udaZ http://www.y7YwKx7Pm6OnyJvolbcwrWdoEnRF29pb.com

 chaba          29.01.2017 19:34:03

8gPNvI http://www.y7YwKx7Pm6OnyJvolbcwrWdoEnRF29pb.com

 chaba          29.01.2017 15:47:00

2HPkrR http://www.y7YwKx7Pm6OnyJvolbcwrWdoEnRF29pb.com

 matt          29.01.2017 15:38:52

HaxLWn http://www.y7YwKx7Pm6OnyJvolbcwrWdoEnRF29pb.com

 Barnypok          07.01.2017 19:10:36

F5CAED http://www.FyLitCl7Pf7ojQdDUOLQOuaxTXbj5iNG.com

 Barnypok          06.01.2017 14:02:53

LjbVJE http://www.FyLitCl7Pf7ojQdDUOLQOuaxTXbj5iNG.com

 Barnypok          03.01.2017 10:34:57

uVgczJ http://www.FyLitCl7Pf7ojQdDUOLQOuaxTXbj5iNG.com

 Barnypok          02.01.2017 1:36:13

l1Tiuv http://www.FyLitCl7Pf7ojQdDUOLQOuaxTXbj5iNG.com

 Barnypok          30.12.2016 17:40:27

rqKOiQ http://www.FyLitCl7Pf7ojQdDUOLQOuaxTXbj5iNG.com

 Barnypok          27.12.2016 23:37:08

ud2SXX http://www.FyLitCl7Pf7ojQdDUOLQOuaxTXbj5iNG.com

 Тарас          07.11.2016 20:47:00

Хороша стаття, автор молодець,

Copyright © 2014-2017 <b&r Corp/>