Artykuł

freeimages.com freeimages.com
cze 23 2014
0

Filtry uwierzytelniania w ASP.NET MVC

W poprzednim tekście zainicjowałem mały cykl poświęcony filtrom w ASP.NET MVC. Napisałem tam m.in. jakie jest ich znaczenie oraz zastosowanie. Dziś zgodnie z obietnicą kolejna część tego tematu, która w całości zostanie poświęcona filtrom uwierzytelnienia, czyli nowości wprowadzonej w ASP.NET MVC 5.

Uwierzytelnienia a autoryzacja

Na początek warto zdać sobie sprawę z zasadniczej różnicy pomiędzy uwierzytelnianiem (authentication) a autoryzacją (authorization). Oba pojęcia występują bardzo blisko siebie i niestety czasami różnica pomiędzy nimi łatwo się zaciera. Zacznijmy więc od początku.

Gdy próbujesz się logować do jakiegokolwiek systemu, to z reguły podajesz Twój unikalny identyfikator (ostatnio bardzo często jest to adres email) oraz hasło. Po wprowadzeniu poprawnych danych, zostajesz uwierzytelniony - innymi słowy system wie kim jesteś.

Gdy system już Cię rozpoznaje, to wtedy może określić czy jesteś autoryzowany do wykonania określonej czynności, czy też nie. Przykładowo - możesz mieć dostęp do systemu, ale Twój poziom uprawnień może nie pozwalać na czynności administracyjne.

Mówiąc krótko - uwierzytelnianie to potwierdzenie Twojej tożsamości, natomiast autoryzacja to weryfikacja Twoich uprawnień do określonych funkcjonalności.

IAuthenticationFilter

Filtry uwierzytelnienia są na swój sposób wyjątkowe. Po pierwsze zostały one wprowadzone dopiero w ASP.NET MVC w wersji 5, po drugie jako jedyne nie posiadają domyślnej implementacji, co pewnie po części wynika z czynnika pierwszego. Wcześniej potrzebną funkcjonalność umieszczano po prostu w filtrach autoryzacji. Nowe podejście pozwala na odpowiednie działanie już na samym początku drogi. Jeśli dostęp jest chroniony, to po co w ogóle próbować autoryzować użytkownika, w momencie gdy nie przedstawił się on poprawnie?

Wyjściowo nasz interfejs zawiera dwie metody, które powinniśmy zaimplementować w klasie naszego filtru. Tak jak wspomniałem już w pierwszym tekście, określone metody muszą znaleźć się w klasie atrybutu, ale niekoniecznie muszą one zostać wypełnione. Wypełniacie kodem tylko tą metodę, która jest w Wam w danej chwili potrzebna (pamiętajcie by nie rzucać wyjątku!).

public interface IAuthenticationFilter
{
	void OnAuthentication(AuthenticationContext 
		filterContext);
	void OnAuthenticationChallenge(AuthenticationChallengeContext 
		filterContext);
}

Pierwsza z metod wywoływana jest przed akcją oraz przed wszystkimi innymi filtrami, natomiast druga wywoływana jest tuż przed operacją zwrócenia rezultatu (zaraz po wykonaniu akcji). W obu metodach mamy dostęp do kontekstu. Zarówno w pierwszym jak i drugim przypadku, kontekst udostępnia właściwości:

public ActionDescriptor ActionDescriptor { get; set; }
public ActionResult Result { get; set; }

Dodatkowo w kontekście dla metody OnAuthentication znajdziecie również właściwość Principal:

public IPrincipal Principal { get; set; }

W ten sposób w pierwszej z metod jesteśmy w stanie sprawdzić czy użytkownik jest uwierzytelniony, czy też nie. W zależności od wyniku tego sprawdzenia możemy ustawić nowy obiekt typu ActionResult:

public void OnAuthentication(AuthenticationContext filterContext)
{
    IIdentity ident = filterContext.Principal.Identity;
    if (!ident.IsAuthenticated)
    {
        filterContext.Result = new HttpUnauthorizedResult();
    }
}

Ma to dość istotne znaczenie, ponieważ jeśli tego nie zrobimy, akcja zostanie wykonana normalnie. W przeciwnym razie użytkownik od razu zostanie przekierowany do drugiej z metod, czyli do OnAuthenticationChallenge:

public void OnAuthenticationChallenge(AuthenticationChallengeContext 
    filterContext)
{
    if (filterContext.Result == null || filterContext.Result 
        is HttpUnauthorizedResult)
    {
        filterContext.Result = new RedirectToRouteResult(
            new RouteValueDictionary {
            {"controller", "Account"}, 
            {"action",  "Login"}, 
            {"returnUrl", filterContext.HttpContext.Request.RawUrl}
        });
    }
}

Moglibyście teraz zapytać - dlaczego nie ustawić przekierowania do strony logowania od razu w pierwszej z metod? Teoretycznie można by tak uczynić, ale w obecnym wariancie druga z metod jest również w stanie obsłużyć negatywny wynik autoryzacji (o autoryzacji przeczytacie w kolejnym odcinku). W ten sposób cała logika została bardzo elegancko podzielona i każda metoda zajmuje się dokładnie tym czym powinna:-)

Poniżej całościowy listing klasy atrybutu. Tak jak wspomniałem w pierwszym tekście, tworząc nowy atrybut implementujemy wybrany interfejs oraz rozszerzamy klasę FilterAttribute:

public class MyAuthenticationFilterAttribute : FilterAttribute, 
    IAuthenticationFilter
{
    public void OnAuthentication(AuthenticationContext filterContext)
    {
        IIdentity ident = filterContext.Principal.Identity;
        if (!ident.IsAuthenticated)
        {
            filterContext.Result = new HttpUnauthorizedResult();
        }

    }

    public void OnAuthenticationChallenge(AuthenticationChallengeContext 
        filterContext)
    {
        if (filterContext.Result == null || filterContext.Result 
            is HttpUnauthorizedResult)
        {
            filterContext.Result = new RedirectToRouteResult(
                new RouteValueDictionary {
                {"controller", "Account"}, 
                {"action",  "Login"}, 
                {"returnUrl", filterContext.HttpContext.Request.RawUrl}
            });
        }
    }
}

Poniżej również przykładowy kod kontrolera wykorzystanego w tekście:

public class HomeController : Controller
{
    [MyAuthenticationFilter]
    public ActionResult Index()
    {
        return View();
    }
}

Oczywiście w tym momencie aplikacja wysypie się błędem ponieważ brakuje strony logowania. Nasz filtr zadziała jednak poprawnie;-)

Materiały

Cykl

Data ostatniej modyfikacji: 02.08.2014, 13:24.

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus