Ile kosztuje stworzenie dedykowanej aplikacji dla firm

Ile kosztuje stworzenie dedykowanej aplikacji dla firm

Stało się! W końcu zdecydowałeś aby rozwinąć skrzydła, w Swoim biznesie i przyspieszyć jego rozwój, długo się zastanawiałeś/aś nad potrzebami Twojej firmy, nad miejscami gdzie można coś usprawnić. Twój biznes ma bardzo specyficzna procesy i potrzebuje czegoś ekstra, czegoś, czego nie można zaspokoić dostępnymi rozwiązaniami na rynku. Jedynym wyjściem aby ruszyć mocno do przodu jest dedykowane oprogramowanie. Pewnie zacząłeś/aś zastanawiać się czy Was na to stać? Ten artykuł będzie doskonałym źródłem informacji dla właścicieli firm, managerów lub przyszłych przedsiębiorców którzy chcą wejść w biznes.

Ile kosztuje aplikacja dla firm?

To zależy, to najczęściej słyszana odpowiedź, gdy zapytamy firmę o koszt aplikacji dla Naszego przedsiębiorstwa. I fakt, koszty aplikacji mogą się diametralnie różnić. Nieraz dla nas bardzo prosta funkcjonalność, może implikować duże wyzwanie dla programistów przez co wycena rozwiązania może się nam wydawać oderwana od rzeczywistości. Dokładna wycena aplikacji zazwyczaj poprzedzona jest szeregiem spotkań, konsultacją wymagań z użytkownikami, decyzjami odnośnie technologii do wykorzystania czy też przygotowaniem pełnego projektu rozwiązania. Rzadko firmy są w stanie na samym początku podać dokładne koszty aplikacji. Ale, dobrze trafiłeś, w tym artykule spróbujemy zmierzyć się z tym tematem nie będzie to dokładne, ale pozwoli Tobie zobrazować sobie jakiej inwestycji potrzebujesz i jakie decyzje podjąć. Aby ruszyć dalej musimy założyć sobie ramy w jakich będziemy się poruszać.

No to w skrócie. Średnio na stworzenie dedykowanej aplikacji będzie trzeba wydać pomiędzy 10 000 a 300 000 zł. Dokładny koszt zależy od tego, ile integracji i funkcji jest potrzebnych, aby stworzyć aplikację, której potrzebujesz. Dla dalszych kalkulacji przyjmijmy trzy podstawowe scenariusze.

Największym czynnikiem kosztowym są funkcjonalności jakie Nasza aplikacja powinna posiadać. Wiem, że papier przyjmie wszystko, każda funkcjonalność wydaje się nam niezbędna, tylko to kosztuje! i może kosztować całkiem sporo . Ze swojego doświadczenia mogę polecić jedną technikę, która pomoże nam określić co faktycznie potrzebujemy. Chodzi mianowicie o Event Storming bardzo prosta technika a potrafi zaoszczędzić nam masę pieniędzy poprzez nie robienie rzeczy zbędnych.

Jakie czynniki  wpływają na koszt tworzonej aplikacji dla firmy?

Koszt wykonania aplikacji zależą od trzech rzeczy:

  • Poziom złożoności aplikacji,
  • Wygląd aplikacji,
  • Platformy na której będzie działać.

Poniżej omówię bardziej szczegółowo każdy z nich, zaczynając od złożoności.

Poziom złożoności przy tworzeniu aplikacji

Wytworzenie funkcji dla użytkowników i integracje z zewnętrznymi dostawcami są podstawowym czynnikiem kosztowym Naszej aplikacji. To od ilości funkcji, poziomu ich złożoności zależy finalny koszt aplikacji.  Twórcy oprogramowania potrzebują czasu, zapoznania się z biznesem aby określić koszt implementacji. Stąd porównanie oprzemy na wcześniejszych wspomnianych scenariuszach.

Prosta aplikacja:

Poziomy złożoności i funkcjonalności:

  • Podstawowe funkcje, trzy do czterech podstron, formularz kontaktowy, nie skomplikowane funkcjonalności.
  • Główne funkcje jakie będzie posiadała:
    • Logowanie użytkowników,
    • Rejestracja użytkowników,
    • Tworzenie i edycja profili,
    • Prostą wyszukiwarkę,
    • Panel administracyjny lub dashboard.

Przykład:

  • Kalkulator kosztów,
  • Konfigurator produktu.

Aplikacja z podstawowymi funkcjami, prostą bazą danych i API:

Każdorazowe dodanie nowych funkcji zwiększa koszt wytworzenia aplikacji.

Poziomy złożoności i funkcjonalności:

  • To co w przypadku „Prostej aplikacji” powyżej,
  • Integracja z mediami społecznościowymi,
  • Zakupy,
  • Płatności,
  • Funkcje oparte o lokalizację geograficzną,
  • Ulepszone bezpieczeństwo aplikacji,
  • Możliwość wykorzystania infrastruktury chmurowej,
  • Optymalizację zasobów – w zależności od liczby użytkowników,
  • Udostępnienie wybranych funkcji w postaci REST API dla integracji z naszą aplikacją.

Przykład:

  • Strona firmowa z konfiguratorem i możliwością zakupu produktu,
  • Aplikacja do udostępniania zdjęć.

Zaawansowana aplikacja z wieloma specyficznymi funkcjami, skomplikowaną bazą danych i integracjami z dostawcami:

Całkowity koszt wykonania takiej aplikacji może znacznie przekroczyć to, czego można się spodziewać.  W przypadku takich aplikacji, to nie tylko koszt złożoności wpływa na jej cenę. Dochodzi do tego bardzo kosztowny research, planowanie, wizualizacje ekranów czy też wytworzenie natywnej wersji aplikacji.  Kolejnym aspektem na jaki musimy zwrócić uwagę jest infrastruktura, czyli to wszystko co nam potrzebne aby uruchomić naszą dedykowaną aplikację (serwery, bazy danych, kopie zapasowe itp.)

Poziomy złożoności i funkcjonalności:

  • To co w przypadku „Aplikacja z podstawowymi funkcjami prostą bazą danych i API” powyżej,
  • System ocen, rekomendacji,
  • Synchronizacja danych,
  • Integracja z dostawcami,
  • Funkcje natywne,
  • Streaming online,
  • Wiadomości.

Przykład:

  • aplikacje do dostarczania żywności,
  • aplikacja dla firm taksówkarskich,
  • aplikacje korporacyjne.

Wygląd aplikacji

Faza projektowania i programowania funkcjonalności nie jest jedyną, kolejną istotną częścią aplikacji internetowych jest ich wygląd. Dla niektórych naszych przyszłych użytkowników aplikacji dla firm wygląd jest najważniejszy. W przypadku projektowania interfejsu zasada jest identyczna jak w poprzedniej fazie im więcej funkcjonalności wizualnych tym koszty szybciej rosną. Dobrą alternatywą może się okazać wykupienie gotowych szablonów, czy gotowe UI Kit (są to gotowe zestawy wizualne komponentów, z których składamy nasza aplikację).  Poniżej przedstawię kilka istotnych elementów, nad którymi powinieneś przemyśleć i zdecydować w którą stronę pójść.

Interfejsy użytkownika i wizualizację:

Co wpływa na koszty:

  • Przede wszystkim przeznaczenie aplikacji, przykładowo niskie koszta programowania wygeneruje aplikacja dla firm, która ma za zadanie tylko poinformowania o usługa/produktach jakie oferuje , a dużo drożej przyjdzie nam nam zapłacić za zaprogramowanie aplikacji, która ma za zadanie sprzedaż np|: sklep internetowy , marketplace itp.
  • Liczba okien/widoków dla użytkownika oraz ich przeznaczenie. Dla przykładu niższy koszt wygeneruje nam 5 stron statycznych zawierających informacje o firmie, dane kontaktowe, opis jej usług. W przypadku gdy tymi 5 stronami będą interaktywne formularze, to szybko rośnie złożoność a tym samym ostateczna cena.

Przykłady do zastanowienia się, jak to ma wyglądać w naszej aplikacji:

  • Szablon aplikacji,
  • Ekrany,
  • Kolory,
  • Typografię,
  • Wiodące kształty,
  • Specyficzne dla nas elementy np: konfigurator oferty.

 User Experience:

Jednym z popularniejszych stwierdzeń dlaczego potrzebujemy się skupić w aplikacji dla firmy na jej łatwości obsługi przez użytkowników jest „Użytkownicy ignorują design, który ignoruje użytkowników”. Nowoczesne aplikacje internetowe nie mogą pozwolić sobie na zaniedbania w tym aspekcie. Konkurencja nie śpi. Często okazuje się, że mając dużo lepszy produkt, konkurencja nam ucieka ponieważ jej aplikacja jest bardziej intuicyjna i prostsza w obsłudze. A to niestety kosztuje. patrząc z drugiej strony, gdy tworzymy aplikację dla niewielkiej grupy pracowników cześć z prac jak makiety zachowań możemy sobie odpuścić, podczas pracy nad aplikacją programiści najczęściej pracują z pracownikami i od nich czerpią wiedzę. Dodatkowo często praktykowanym rozwiązaniem jest odbieranie poszczególnych etapów przez właśnie pracowników i to powinno nam załatwić sprawę.

Co wpływa na koszty:

  • Analizy – user expirience to nie programowanie, a planowanie/resarch jak Nasi użytkownicy będą wykorzystywać naszą aplikację. Większą cześć czasu zjedzą nam spotkania z interesariuszami, może badania/konsultacje z potencjalnymi użytkownikami. Jest to kosztowny proces, ale przyniesie na pewno wymierne korzyści. Dlatego zastanów się, czy i co wiesz o Swoich przyszłych użytkownikach, to kluczowe!
  • Makiety zachowań – ilość, tzn. jak dużo planujemy ścieżek przejść dla użytkownika np: ścieżka dodania produktu do koszyka, uruchomienie raportu czy przeprowadzenie wizyty lekarskiej. Im więcej mamy dedykowanych ścieżek tym szybciej koszty Naszej aplikacji rosną.

Przykłady do zastanowienia się, jak to ma wyglądać w naszej aplikacji:

  • Rozmieszczenie przycisków,
  • Gra kolorami,
  • Wizualizacja ścieżek użytkowników.

Branding:

Jak nasza aplikacja, firma ma być identyfikowana. Ciężko jest wycenić tę część, zazwyczaj zlecamy to firmom zewnętrznym wyspecjalizowanym  w tej dziedzinie. Równie dobrze może Nas to kosztować 100 zł na Allegro, 500-1500 zł u freelancera czy kilkanaście, kilkadziesiąt tysięcy w renomowanej agencji.

Co wpływa na koszty:

  • Kto będzie wykonywał dla Nas tą prace: kupimy gotowy zestaw na popularnych marketplace-ach,  freelancer czy agencja kreatywna.

Przykłady do zastanowienia się, jak to ma wyglądać w naszej aplikacji:

  • Logo,
  • Kolorystyka,
  • Działania marketingowe do podjęcia.

Platforma

A mianowicie gdzie i na czym będzie nasza aplikacja dla firm uruchomiona dla użytkowników. Przykładowo, czy aplikacja ma być przeznaczona na system Android, iOS, Windows, czy może ma być dostępna z poziomu przeglądarki, a może na każdej powyższej platformie. Decyzja gdzie Nasza aplikacja będzie uruchamiana będzie miał znaczący wpływ na koszta jakie poniesiemy przy jej wytworzeniu.

Do rozważenia:

  • Jaki udział dla poszczególnych platform ma Twój segment użytkowników, może 80% z nich korzysta tylko z Androida i nie ma sensu tworzyć wersji na iOS, a może wykorzystują tylko przeglądarkę i sporadycznie Androida. Dzięki temu można wykonać pełną funkcjonalność dla przeglądarek a okrojoną dla Androida.
  • Czy potrzebujesz wersji natywnej? Czyli takiej, która działa na obu platformach (przeglądarka, Android lub iOS) niezależnie. Jeżeli zdecydujesz się na takie rozwiązanie proces wytworzenia aplikacji internetowej może się podwoić…

Jakiego wykonawcę wybrać?

Najważniejszy czynnik cenowy Naszej aplikacji. To od tego na jakiego wykonawcę się zdecydujemy będzie zależał ostateczny koszt aplikacji no i powodzenie projektu. Wiele zależy tu od doświadczenie, zgranie zespołu, czy też dziedzinie, gałęzi biznesu w jakich wcześniej wykonywane były przez nich projekty. Przecież może się okazać, że zgłosiła się firma/freelancer/agencja która mogła wykonywać aplikację dla jednego z Twoich konkurentów i ma już wiedzę, nie będzie konieczne wiele spotkań a w ostatecznym rachunku wyjdzie taniej. Poniżej kilka moich propozycji do rozważenia.

Software house:

  • Koszty:
    • Nie są tani, koszty wytworzenia będą znacząco większe od np: wynajęcia freelancera. W zależności od doświadczenia, aktualnego obłożenia projektami cena za roboczo godzinę powinna się wahać od 150 zł – 400 zł.  Tak więc jeśli całkowita praca obejmie 400h dla prostej aplikacji z naszych przykładów, za stawkę 200 zł/h musimy się liczyć z całkowitym kosztem w okolicach 80 000 zł.
  • Wady i zalety wyboru:
    • + Utrzymywanie stałej komunikacji z zespołem wytwórczym,
    • + Większa pewność, że pracę wykonują profesjonaliści,
    • – Zmiany w trakcie prac mogą dużo kosztować,
    • – Nie podejmują się niewielkich projektów, chyba że za odpowiednio wyższa składkę.

Własny zespół:

Jeżeli chcemy pełnej przejrzystości, łatwej komunikacji i godnego zaufania rozwiązania Musisz zatrudnić swój własny zespół

  • Koszty:
    • Wynagrodzenie pracowników – może troszkę przytłaczać. Przykładowo zatrudnienie jednego doświadczonego programisty to koszt rzędu 200 000 zł rocznie, załóżmy że na początku będzie ich dwóch, programista aplikacji mobilnych ok 160 000 zł rocznie, projektant aplikacji 180 000 zł rocznie. Także jak Sam widzisz koszty nie są małe.
  • Wady i zalety wyboru:
    • + Komunikacja jest super płynna, a wszyscy w zespole będą w pełni zaangażowani w projekt,
    • -+ Jakość, szybkość wytworzenia aplikacji zależeć będzie od tego kogo zatrudnisz i za ile,
    • – Dodatkowe koszty, wynagrodzenie to nie jedyny koszt, gdzie szkolenia, biuro, stanowisko pracy, licencje, które mogą sięgać kilkudziesięciu tysięcy złotych za stanowisko.

Zatrudnienie freelancera:

Najtańsza opcja w zestawieniu. Tu wszystko zależy od osoby z jaką się zwiążesz, jakość komunikacji, dostępność komunikacji, jakość kodu, czy też wykorzystane narzędzia. Każdy programista freelancer pracuje w dobrze sobie znanej technologi, jeżeli nie masz jej wcześniej sprecyzowanej wszystko w porządku, wystarczy wybrać odpowiadającą Nam osobę, jeżeli mamy określoną technologię wykonania to wybór troszkę nam się zawęży. Według mnie musisz rozważyć dwie opcje zatrudnienia freelancera,

  1. Za wykonaną pracę – rozliczane na podstawie wcześniej wycenionego kosztu danej funkcjonalności np: rejestracja i logowanie się użytkowników zostało wycenione w trakcie planowania na 1200 zł i taką kwotę zapłacisz po odebraniu pracy.
  2. Za czas pracy – rozliczane na podstawie czasu spędzonego nad funkcjonalnością np: rejestracja i logowanie się użytkowników zostało wycenione na 12h licząc po stawce 100zł/h koszt wyniesie 1200 zł. Musisz wziąć pod uwagę, że niemal zawsze wycena czasochłonności przez programistę jest zaniżona i często  będzie ona bardziej pracochłonna a zarazem droższa.
  • Koszty:
    • Stawki godzinowe początkującego freelancera zaczynają się od 50 zł i w zależności od doświadczenia rosną np: programista z bogatym w różnorodne projekty doświadczaniem może kosztować w granicach 100 -150 zł/h.
  • Wady i zalety wyboru:
    • + Najtańsza metoda wytworzenia aplikacji dla firm,
    • + Nieduży koszt wprowadzanych zmian,
    • – Odpowiedzialność, nie raz się zdarza, że porzuci projekt w trakcie trwania,
    • – Może go przytłoczyć złożoność rozwiązania,
    • +- Zależy od człowieka, komunikacja. Z częścią będzie się pracowało przejrzyście i szybko, a z innymi może być utrudniona np: gdy pracuje tylko w weekendy.

Ukryte koszta

Często zleceniodawcy aplikacji nie zdają sobie sprawy z ich istnienia, a w dużej mierze mają one duży wpływ na powodzenie i rentowność aplikacji dla firm. Poniżej kilka przykładów, których musisz być świadom decydując się na dedykowana aplikację:

  • koszty zarządzania, i utrzymania aplikacji. Jeżeli sam nie będziesz jej obsługiwał to potrzebne będzie zatrudnienie kogoś do tej pracy. Druga sprawa, błędy w aplikacji zawsze się zdarzają i trzeba będzie je poprawić, i w zależności od wykonawcy projektu, umowy koszty mogą być spore. Może się zdarzyć, że poprawa błędu zajmuje niemal tyle ile koszt wytworzenia funkcjonalności.
  • Koszty infrastruktury. Do uruchomienia aplikacji potrzebujemy serwerów, baz danych, może dedykowanych usług chmurowych to wszystko kosztuje. I czasem całkiem sporo. Dlatego myśląc o aplikacji dedykowanej jeżeli już na nią się zdecydujesz skonsultuj dokładnie te koszta z wykonawcą. Mogą znacząco wpłynąć na rentowność projektu.

 

Jeżeli bliżej Ci do zatrudnienia doświadczonego freelancera niż do zlecenia prac w software house zapraszam do kontaktu.

Ongo Homes of your back office systems provides 10,000 homes for people

Overview

Sure, the technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Tenet has remained constant: we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Problems

Sure, the technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Challenge

Technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Solution

Technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Ongo Homes of your back office systems provides 10,000 homes for people

Overview

Sure, the technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Tenet has remained constant: we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Problems

Sure, the technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Challenge

Technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Solution

Technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Virtual environment in order to scale up their back office of your back office systems

Overview

Sure, the technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Tenet has remained constant: we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Problems

Sure, the technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Challenge

Technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Solution

Technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Ongo Homes of your back office systems provides 10,000 homes for people

Overview

Sure, the technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Tenet has remained constant: we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Problems

Sure, the technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Challenge

Technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Solution

Technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Virtual environment in order to scale up their back office of your back office systems

Overview

Sure, the technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Tenet has remained constant: we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Problems

Sure, the technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Challenge

Technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Solution

Technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Ongo Homes of your back office systems provides 10,000 homes for people

Overview

Sure, the technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Tenet has remained constant: we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Problems

Sure, the technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Challenge

Technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Solution

Technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Virtual environment in order to scale up their back office of your back office systems

Overview

Sure, the technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Tenet has remained constant: we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Problems

Sure, the technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Challenge

Technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Solution

Technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Ongo Homes of your back office systems provides 10,000 homes for people

Overview

Sure, the technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Tenet has remained constant: we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Problems

Sure, the technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Challenge

Technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Solution

Technology has come a long way since then, and the variety of the information objects we’re managing has changed a lot, but one tenet has remained constant we’ve always focused on the intersection of people, processes, and information. As the Association for Intelligent Information Management, we help organizations put their information to work.

Autoryzacja i uwierzytelnienie w ASP.NET Core za pomocą IdentityServer 4 – Implementacja – część 2

Po przydługim wpisie z czystą teorią na temat IdentityServer 4, OAuth 2.0 i OpenID Connect w ASP.NET Core przyszedł czas na coś fajniejszego. Praktyczna implementacja IdentityServer 4. W trakcie tego posta stworzymy gotowe rozwiązanie oparte na powyższych technologiach plus kilku dodatkowych, które przedstawię poniżej. Oczywiście przykład oparty będzie na use case z poprzedniego posta.

Końcowym efektem będzie działająca aplikacja jak na filmiku poniżej:

Czego będziemy potrzebowali:

  1. IDE – Visual Studio, Code, co kto lubi,
  2. MS SQL – przykład jest na nim oparty, ale zawsze możesz użyć czegoś co lubisz
  3. .NET Core 3.1
  4. Biblioteki dla IdentityServer 4

Jak już wspomniałem w poprzednim wpisie, nowoczesne aplikacje do uwierzytelniania i autoryzacji użytkowników wykorzystują protokoły OAuth2.0 i OpenID Connect. Ręczna ich implementacja może zająć nam miesiące a i tak nie mamy pewności czy czegoś nie poknociliśmy. Z pomocą przychodzi nam ich gotowa implementacja za pomocą IdentityServer 4. Tak naprawdę jest on gotowcem, którego wystarczy zainstalować i możemy działać. Warto wspomnieć, że ma świetną dokumentację, którą znajdziecie tu IdentityServer 4  oraz dobrze przedstawione przykłady, które znajdują się tutaj: Samples.

Jeżeli przeczytałeś poprzedni post pewnie już wiesz, że IdentityServer 4 ma zaimplementowane kilka procesów dla autoryzacji użytkownika, w moim przykładzie wykorzystałem Implicity, który przedstawia się następująco:

Implicity GrantSzczegółowy opis poszczególnych kroków, które są wykonywane znajduje się we wstępnym poście.

Tworzymy strukturę projektu:

W przykładowym use case opisanym tu będziemy potrzebować w aplikacji trzech projektów. Pierwszy odpowiedzialny będzie za autoryzację i uwierzytelnienie użytkowników, zadaniem drugiego będzie udostępnienie zastrzeżonych zasobów i oczywiście projekt naszego klienta w naszym przypadku będzie to aplikacja www. Nie chcę tu wchodzić w szczegóły, ale jakiś opis jest potrzebny. Wszystkie trzy projekty oparte są o ASP.NET Core Web Application i .NET Core 3.1. W przypadku projektu:

  • dla uwierzytelnienia i autoryzacji jest to aplikacja MVC,
  • zastrzeżonym zasobem będzie API
  • klient oparty jest na templatce Angular 8  w Visual Studio.

Poniżej struktura solucji:

struktura projektu Implementacja IdentityServer 4 w ASP.NET Core

Autoryzacja i uwierzytelnienie:

Implementacje autoryzacji i uwierzytelnienia wykonałem w projekcie FR.IdentityServer. Za zadanie będzie miał właśnie uwierzytelnić i autoryzować użytkowników naszej aplikacji. W pierwszej kolejności potrzebujemy zainstalować trzy paczki nugetowe dla IdentityServer 4:

IdentityServer 4 Nuget

Pierwsza paczka IdentityServer4 zapewnia nam podstawowe funkcjonalności które posiadają implementację OAuth2.0 i OpenID Connect. Ponieważ przykład oparty jest o ASP.NET Core potrzebujemy jeszcze dwie dodatkowe biblioteki IdentityServer4.AspNetIdentity oraz IdentityServer4.EntityFramework. Pierwsza z nich zapewnia nam bezproblemową integrację z naszą aplikacją, druga pomoże nam zachować nasze ustawienia i użytkowników zgodnie z flow w bazie danych.

IdentityServer 4 wykorzystuje kilka tabel infrastrukturalnych, w których przechowuje dane konfiguracyjne jak zdefiniowane zasoby, klientów oraz dane operacyjne jak tokeny, czy zgody. Kiedy Zintegrujesz bibliotekę z bazą danych powstanie kilkanaście tabel jak niżej:

IdentityServer 4 i EntityFramework struktura

Aby taką strukturę uzyskać musimy odpowiednio skonfigurować IdentityServer 4 w naszej klasie Starup.cs. W pierwszej kolejności musimy podłączyć nasz DbContext. W naszym przypadku będą to aż trzy konteksty. Pierwszy  ApplicationDbContext, który wykorzystuje ASP.NET Core Identity do przechowywania danych naszych użytkowników loginy, hasła itp. PersistedGrantDbContext i ConfigurationDbContext, które dostarcza nam IdentityServer 4.

Krok 1: Dodanie ApplicationDbContext i podłączenie do bazy FreelancerIdentity:

services.AddDbContext<ApplicationDbContext>(o =>
            {
                o.UseSqlServer(Configuration.GetConnectionString("FreelancerIdentity"));
            });

            services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

Krok 2: Musimy wskazać gdzie będą przechowywane ustawienia IdentityServer 4 i zasoby użytkowników:

 var builder = services.AddIdentityServer()
                .AddConfigurationStore(options =>
                {
                    options.ConfigureDbContext = o =>
                    {
                        o.UseSqlServer(Configuration.GetConnectionString("FreelancerIdentity"), so =>
                        {
                            so.MigrationsAssembly(typeof(Startup).Assembly.GetName().Name);
                        });

                    };
                })
                .AddOperationalStore(options =>
                {
                    options.ConfigureDbContext = o =>
                    {
                        o.UseSqlServer(Configuration.GetConnectionString("FreelancerIdentity"), so =>
                            {
                                so.MigrationsAssembly(typeof(Startup).Assembly.GetName().Name);
                            });

                    };

                    // this enables automatic token cleanup. this is optional.
                    options.EnableTokenCleanup = true;
                })

W ostatniej linijce na screenie powyżej dodajemy naszego użytkownika, w tym przypadku jest to custowmowy użytkownik dziedziczący po IdentityUser z AspNetCore.Identity.

public class ApplicationUser : IdentityUser
    {
        public string Name { get; set; }
        public string CompanyName { get; set; }
        public string PictureUrl { get; set; }
    }

Do tej pory wszystkie dane przechowywane są w bazie danych. Istnieje jeszcze możliwość przechowywania ich w pamięci. Aby to zrobić należy do konfiguracji z Kroku 2 dodać poniższe trzy linijki. Pierwsza dodaje zasoby do jakich będziemy mieli dostęp, druga chronione przez nas API, trzecia konfiguracje aplikacji klienckich, które będą się łączyć do naszego serwera autoryzacyjnego:

                .AddInMemoryIdentityResources(IdentityServerConfiguration.GetIdentityResources())
                .AddInMemoryApiResources(IdentityServerConfiguration.GetApis())
                .AddInMemoryClients(IdentityServerConfiguration.GetClients())
                .AddAspNetIdentity<ApplicationUser>();

Krok 3: Konfigurujemy nasze zasoby:

Potrzebujemy do tego trzy rzeczy, o których już wspomniałem powyżej.

1. Dozwolone zakresy zgodnie z OpenID Connect. Mi potrzebne będa trzy: OpenId, Email i Profile.

public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Email(),
                new IdentityResources.Profile(),
            };
        }

2. Nasze zasoby. Czyli API, które chronimy.

public static IEnumerable<ApiResource> GetApis()
        {
            return new List<ApiResource>
            {
                new ApiResource("FinanceManagerAPI", "Finance Manager service")
            };
        }

3. Aplikacje kliencką, z której będziemy wykonywać requesty.

 public static IEnumerable<Client> GetClients()
        {
            return new List<Client>
            {
                new Client()
                {
                    ClientId = "spa",
                    ClientName = "freelancer web app",
                    AllowedGrantTypes = GrantTypes.Implicit,
                    AllowAccessTokensViaBrowser = true,
                    RedirectUris = {"http://localhost:4200/auth-callback"},
                    RequireConsent = false,
                    PostLogoutRedirectUris = {"http://localhost:4200/"},
                    AllowedCorsOrigins = {"http://localhost:4200"},
                    AllowedScopes =
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        IdentityServerConstants.StandardScopes.Email,
                        "FinanceManagerAPI"
                    }
                }
            };
        }

Tutaj wypada więcej powiedzieć. ClientId to identyfikator aplikacji z której będziemy się dobijać. AllowedGrantTypes określa proces dla autoryzacji użytkownika, ja wybieram Implicity. AllowAccessTokensViaBrowser pozwalamy na przekazywanie tokenów. RedirectUris określa gdzie wrócimy, gdy użytkownik zostanie już uwierzytelniony. RequireConsent mówi, czy wyświetlamy widok ze zgodami na dostęp do zakresów patrz pkt 1. PostLogoutRedirectUris określa gdzie trafimy jak się wylogujemy. AllowedCorsOrigins podajemy url z którego będziemy wykonywać requesty. AllowedScopes dozwolone dla aplikacji zakresy.

Krok 4: Zapisujemy nasze dane do bazy:

Jak już mamy wszystko skonfigurowane. Kto gdzie i jak będzie miał dostęp. Pozostaje nam tylko utrwalić to w bazie danych. Do tego celu możemy wykorzystać customowego DatabaseInitializer-a. Mój wygląda jak niżej:

 public class DatabaseInitializer
    {
        public static void Init(IServiceProvider provider, bool useInMemoryStores)
        {
            if (!useInMemoryStores)
            {
                provider.GetRequiredService<ApplicationDbContext>().Database.Migrate();
                provider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate();
                provider.GetRequiredService<ConfigurationDbContext>().Database.Migrate();
            }
            InitializeIdentityServer(provider);

            var userManager = provider.GetRequiredService<UserManager<ApplicationUser>>();
            var erloon = userManager.FindByNameAsync("erloon").Result;
            if (erloon == null)
            {
                erloon = new ApplicationUser
                {
                    Email = "erloon@wp.pl",
                    UserName = "erloon",
                    CompanyName = "sparkdata.pl"
                };
                var result = userManager.CreateAsync(erloon, "$AspNetIdentity10$").Result;
                if (!result.Succeeded)
                {
                    throw new Exception(result.Errors.First().Description);
                }

                erloon = userManager.FindByNameAsync("erloon").Result;

                result = userManager.AddClaimsAsync(erloon, new Claim[]{
                    new Claim(JwtClaimTypes.Name, "Maciej Kryca"),
                    new Claim(JwtClaimTypes.GivenName, "Maciej"),
                    new Claim(JwtClaimTypes.FamilyName, "Kryca"),
                    new Claim(JwtClaimTypes.Email, "erloon@wp.pl"),
                    new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
                    new Claim(JwtClaimTypes.WebSite, "https://sparkdata.pl"),
                    new Claim(JwtClaimTypes.Address, @"{ 'street_address': 'localhost 10', 'postal_code': 11146, 'country': 'poland' }", 
                        IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json)
                }).Result;

                if (!result.Succeeded)
                {
                    throw new Exception(result.Errors.First().Description);
                }
                Console.WriteLine("erloon created");
            }
            else
            {
                Console.WriteLine("erloon already exists");
            }
        }

        private static void InitializeIdentityServer(IServiceProvider provider)
        {
            var context = provider.GetRequiredService<ConfigurationDbContext>();
            if (!context.Clients.Any())
            {
                foreach (var client in IdentityServerConfiguration.GetClients())
                {
                    context.Clients.Add(client.ToEntity());
                }
                context.SaveChanges();
            }

            if (!context.IdentityResources.Any())
            {
                foreach (var resource in IdentityServerConfiguration.GetIdentityResources())
                {
                    context.IdentityResources.Add(resource.ToEntity());
                }
                context.SaveChanges();
            }

            if (!context.ApiResources.Any())
            {
                foreach (var resource in IdentityServerConfiguration.GetApis())
                {
                    context.ApiResources.Add(resource.ToEntity());
                }
                context.SaveChanges();
            }
        }
    }

W tym momencie nastał czas na najtrudniejsze. Migracje 😉  Dla ułatwienia przygotowałem kilka gotowych komend.

# Migration instructions for IdentityServer project

* change `UseInMemoryStores` to `false` in **appsettings.json**

### Create migrations
* Add-Migration Initial -Context ConfigurationDbContext -OutputDir "Infrastructure/Database/Migrations/IdentityServer/Configuration"
* Add-Migration Initial -Context PersistedGrantDbContext -OutputDir "Infrastructure/Database/Migrations/IdentityServer/Persisted"
* Add-Migration Initial -Context ApplicationDbContext -OutputDir "Infrastructure/Database/Migrations/Application"

### Update database
* Update-Database -Context PersistedGrantDbContext
* Update-Database -Context ConfigurationDbContext
* Update-Database -Context ApplicationDbContext

Krok 5: Tworzymy endpointy do logowania i rejestracji:

Biblioteka IdentityServer 4 jest tak fajna, że dostarcza nam już gotowe przykłądy, które możemy wykorzystać w naszej aplikacji. Ja skorzystałem i lekko przerobiłem na własne potrzeby gotowe metody do logowania i rejestracji użytkowników. Warto tutaj dobrze wyjaśnić. Aplikacja kliencka w naszym wypadku angular tylko przekierowuje na serwer autoryzacyjny gdzie użytkownik będzie się logował. Dla uproszczenia wykorzystałem strony w ASP.NET MVC.

Logowanie:

Wykorzystuje tutaj gotowy serwis dostarczony przez ASP.NET Identity UserMenager.  Kolejna warta uwagi rzecz to tworzenie Claims-ów użytkownika, które dodajemy do kontekstu. Poźniej w aplikacji klienckiej będziemy mogli z nich skorzystać.

            if (ModelState.IsValid)
            {
                var user = await _userManager.FindByNameAsync(model.Username);
                if (user != null && await _userManager.CheckPasswordAsync(user, model.Password))
                {
                    await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.Name));

                    AuthenticationProperties props = null;
                    if (AccountOptions.AllowRememberLogin && model.RememberLogin)
                    {
                        props = new AuthenticationProperties
                        {
                            IsPersistent = true,
                            ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration)
                        };
                    };

                    await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("userName", user.UserName));
                    await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("name", user.Name));
                    await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("email", user.Email));
                    await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("role", Roles.Consumer));
                    await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("companyName", user.CompanyName));
                  

                    await HttpContext.SignInAsync(user.Id, user.UserName, props);

                    if (context != null)
                    {
                        if (await _clientStore.IsPkceClientAsync(context.ClientId))
                        {
                            return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl });
                        }

                        return Redirect(model.ReturnUrl);
                    }

                    if (Url.IsLocalUrl(model.ReturnUrl))
                    {
                        return Redirect(model.ReturnUrl);
                    }
                    else if (string.IsNullOrEmpty(model.ReturnUrl))
                    {
                        return Redirect("~/");
                    }
                    else
                    {
                        throw new Exception("invalid return URL");
                    }
                }

                await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials"));
                ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage);
            }

Rejestracja:

public async Task<IActionResult> Register([FromBody]RegisterRequestViewModel model)
        {

            if (!ModelState.IsValid)
            {
                var errorsList =
                    ModelState.Keys.SelectMany(key => ModelState[key].Errors.Select(error => new IdentityError()
                    {
                        Code = "Validation",
                        Description = error.ErrorMessage
                    }));

                return BadRequest(errorsList);
            }

            var user = new ApplicationUser { UserName = model.Email, Name = model.Name, CompanyName = model.CompanyName, Email = model.Email, Id = Guid.NewGuid().ToString() };

            var result = await _userManager.CreateAsync(user, model.Password);

            if (!result.Succeeded) return BadRequest(result.Errors);

            await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("userName", user.UserName));
            await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("name", user.Name));
            await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("email", user.Email));
            await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("role", Roles.Consumer));
            await _userManager.AddClaimAsync(user, new System.Security.Claims.Claim("companyName", user.CompanyName));

            return Ok(new RegisterResponseViewModel(user));
        }

W tym momencie to już wszystko. Mamy działający serwer autoryzacyjny oparty na IdentityServer 4. W kolejnej części dodam przykładowe API z chronionymi zasobami i aplikacje kliencką opartą o Angulara.

IdentityServer4 autoryzacja i uwierzytelnienie w ASP.NET Core – Wstęp

Na przestrzeni kilku ostatnich lat aplikacje internetowe, czy mobilne ewoluowały i stały się bardziej kompleksowe, bardziej przyjazne użytkownikom i tym samym bardziej intuicyjne. Aby sprostać nowym wymaganiom dotychczasowe dobrze znane podejścia do autoryzacji i uwierzytelnienia musiały przejść znaczące zmiany. Zazwyczaj mieliśmy do czynienia z bezpośrednią autoryzacją w naszej aplikacji za pomocą LDAP czy ASP.NET Identity, Użytkownik logował się do Naszej aplikacji i korzystał z jej zasobów. Niestety w dużych firmach lub tych zwinniejszych częstym przypadkiem jest posiadanie kilku aplikacji, wielu zasobów online, do których użytkownik musiał się niezależnie logować aby móc normalnie pracować. Powodowało to konieczność posiadania kilku kont i haseł co przysparzało wielu problemów.  We najbliższych wpisach chciałbym przedstawić bardziej praktyczne podejście jakie dostarcza nam ASP.NET Core i IdentityServer 4 wraz z protokołami OAuth2.0 i OpenID Connect.

Dla lepszego zrozumienia nowego podejścia do autoryzacji i uwierzytelnienia za pomocą IdentityServer 4 najlepiej załóżmy sobie przykładowy use case z jakim najczęściej spotkamy się w pracy. A mianowicie mamy aplikację, która za pomocą interfejsu REST API udostępnia naszym użytkownikom funkcjonalność np: zarządzania kosztami i przychodami mikro-przedsiębiorców.. Dodatkowo pod opieką mamy aplikację SPA, która korzysta z tego zasobu. Wiemy, że w przyszłości dojdą dodatkowe niezależne moduły. także trzeba być elastycznym. W dalszej części będę tworzył według mnie najbardziej odpowiednie do tego casa rozwiązanie. Stworzymy moduł odpowiedzialny za autoryzację i uwierzytelnienie, użytkownik będzie logował się tylko raz aby uzyskać dostęp do naszych zasobów API. Postaram się także w trakcie pracy nad tym mini projektem przedstawić możliwe podejścia i wyjaśnić najistotniejsze kwestie. Także zaczynajmy.

Troszkę teorii na początek.

Wiem, ze przed chwilą pisałem, że czysta teoria jest niezrozumiała i wolę praktyczne podejście, ale jakieś podstawowe pojęcie powinniśmy mieć. Inaczej się nie da. Dzięki tym kilku punktom łatwiej zrozumiesz o co tu ta naprawdę chodzi. Dla osób z zerowym doświadczeniem rozjaśni to wiele kwestii – no może lekko przybliży.

 

Co to jest ta autoryzacja i uwierzytelnienie?

Często spotykany problem;) Mylimy znaczenie tych pojęć dlatego szybkie przypomnienie. Uwierzytelnienie to proces  walidacji danych personalnych naszego użytkownika. Sprawdzamy czy osoba, która się loguje faktycznie jest tą osobą za którą się podaje. Natomiast autoryzacja to proces nadawania dostępów do naszych zasobów za pomocą ról, polis itp. Sprawdzamy czy zalogowany użytkownik może uzyskać dostęp do takich czy innych zasobów naszej aplikacji. Przykładowo, w naszym przypadku będzie proste sprawdzenie czy może korzystać z modułu do zarządzania kosztami i przychodami.

Co to jest IdentityServer 4 i do czego go potrzebujemy?

A mianowicie w aplikacji chciałbym mieć tylko jedno miejsce w którym użytkownicy będą uwierzytelniani oraz miejsce, w którym będą przechowywane wszystkie zasady na których udostępniane im są zasoby naszej aplikacji. Nie chcę aby w momencie rozszerzenia funkcjonalności naszej aplikacji konieczne było dorabianie kolejnego providera danych. Użytkownik loguje się raz i ma prosty dostęp do wszystkiego. I tu z pomocą przychodzi nam IdentityServer 4.

IdentityServer 4 jest open source-ową implementacją protokołów OAuth2.0 i OpenID dla aplikacji opartych o .NET Core i .NET. Dzięki niemu nie musimy implementować całej masy rzeczy wymaganych przez powyższe protokoły, tylko korzystamy już z gotowych funkcjonalności. W dalszej części bardziej szczegółowo to opiszę. Na ten moment trzeba wiedzieć, że IdentityServer 4 zapewnia nam kompleksowo autoryzację użytkowników, aplikacji do naszych zasobów. Ważne aby także wiedzieć, że IdentityServer 4 nie zapewnia nam uwierzytelnienia! Do tego wykorzystamy mechanizm dostarczony nam przez framework ASP.NET Core Identity. Dzięki tym dwóm bezpłatnym narzędziom, bez zbędnego wysiłku do naszej dyspozycji oddane zostają gotowe usługi do logowania, rejestracji, obsługi konta użytkownika, muti-factor authentication, czy też obsługa SMS-ów. Zupełnie za free:) Troszkę haseł rzuciłem OAuth2.0, OpenID, a więc co to jest?

Czym jest OAuth2.0?

Czy kiedykolwiek logowałeś się do konta Google? Dodawałeś posta na Instagrama? A może kiedykolwiek sherowałeś linka na swojej tablicy na facebook? Te kilka przykładów to właśnie wykorzystanie OAuth2.0  Ogólnikowo rzecz biorąc protokół ten pozwala na niezawodną i bezpieczną wymianę informacji pomiędzy aplikacjami.

Najważniejsze przypadki użycia OAuth2.0 to:

  • Pozwala użytkownikom zalogować się do naszej aplikacji za pomocą innego konta. Często widzimy, że niektóre aplikacji pozwalają logować się nam za pomocą naszego konta np. na Facebook lub Google. Tą funkcjonalność zapewnia nam właśnie oAuth2.0. Profesjonalnie nazwane to jest „Federated identity”
  • Pozwala jednej usłudze na dostęp do zasobów innej usługi. Przykładowo jedna z aplikacji może poprosić o udostępnienie jej twoich zdjęć na Google drive lub na publikowanie postów w twoim imieniu na Facebook. Tą operację z kolei nazywamy „Delegated authority”.

Federated identity: jest to koncepcja, która umożliwia jednemu usługodawcy (w naszym przypadku to nasza aplikacja) na uwierzytelnienie użytkownika za pomocą innego usługodawcy (np: Google). Dzięki temu użytkownik ma tylko jedno konto oraz miejsce gdzie są przechowywane jego dane. Nie musi zakładać konta u każdego z osobna.

Delegated authority: jest to koncepcja, która daje możliwość uzyskania dostępu do zasobów użytkownika przez usługę lub inną aplikację

Dla lepszego zobrazowania kilka przykładów z życia:

  • Możesz zalogować się na StackOverflow za pomocą konta Google 😉
  • Linkedin często proponuje Ci dodanie swoich kontaktów z Google
  • Możesz publikować tweety z aplikacji mobilnej Twittera

OAuth 2.0 role

Poniżej lista roli jakie odgrywają poszczególni uczestniczy w procesie autoryzacji pomiędzy sobą:

  • Właściciel zasobów (resource owner) – jest to osoba będąca właścicielem zasobu. W większości przypadków można powiedzieć, że jest to końcowy użytkownik,
  • Serwer autoryzacyjny (Authorization server) – Serwer, który wydaje tokeny dostępu dla klienta. Jest to także jednostka, która uwierzytelnia właściciela zasobu i przyznaje autoryzację,
  • Klient (Client) – Aplikacja, która chce uzyskać dostęp do zastrzeżonych zasobów,
  • Serwer zasobów (resource server) – Serwer przechowujący zasoby właściciela zasobów,

Podstawowy proces:

Poniżej opis jak wygląda ogólny proces uwierzytelnienia i autoryzacji:

  1. Klient żąda autoryzacji od właściciela zasobów. Może to zrobić na dwa sposoby, poprzez bezpośrednie przekazanie przez właściciela zasobów, lub poprzez przekierowanie do serwera autoryzacyjnego z linkiem zwrotnym,
  2. Klient otrzymuje upoważnienie reprezentujące właściciela zasobu. OAuth2.0 zapewnia 4 różne zezwolenia. Typ upoważnienie zależy od metody używanej przez klienta do żądania autoryzacji oraz typów obsługiwanych przez serwer autoryzacyjny
  3. Klient używa otrzymanego upoważnienie i żąda tokena dostępu przez punkt końcowy na serwerze autoryzacji
  4. Serwer autoryzacyjny, uwierzytelnia klienta i waliduje upoważnienie. Jeżeli jest ok przekazuje token dostępu
  5. Klient wykorzystuje token do pobierania danych
  6. Serwer zasobów waliduje token i jeżeli jest ok zwraca zasoby

Dzięki IdentityServer 4 i ASP.NET Core nie będziemy musieli implementować tego wszystkiego sami.

Rodzaje klientów:

  • Confidential clients – klient, który jest w stanie zapewnić bezpieczeństwo naszym danym personalnym w naszym przypadku to może być aplikacja ASP.NET Core
  • Public clients – klient, który nie jest zdolny do zapewnienia bezpieczeństwa naszym danym uwierzytelniającym np: aplikacja SPA na angularze

Authorization Grants:

OAuth2.0 udostępnia nam cztery podstawowe typy procesów, które klient może wykorzystać w celu uzyskania upoważnienia do pobrania tokenu dostępowego.

Authorization Code:

Proces poprzez kod autoryzujący jest oparty na przekierowaniach. Co oznacza, że serwer autoryzacji służy jako pośrednik między klientem a właścicielem zasobu. W tym procesie klient przekierowuje właściciela zasobów do serwera autoryzacji za pośrednictwem user-agent.. Po otrzymaniu zgody właściciela zasobu zostaje przekierowany do klienta  z kodem autoryzacyjnym także przez user-agent-a

response_type = code

Authorization Code

  1. Właściciel zasobów jest przekierowany poprzez user-agent do serwera autoryzacyjnego. W kliencie za pomocą którego się łączymy zawarte są zmienne konfiguracyjne jak : identyfikator klienta, żądany zakres (scope), stan lokalny, link przekierowujący do którego serwer autoryzacji przekieruje klienta po przyznaniu lub odmowie dostępu.
  2. Serwer autoryzacji uwierzytelnia właściciela zasobu za pośrednictwem user-agenta. Właściciel zasobu następnie przyznaje lub odrzuca żądanie dostępu klienta do zasobów za pośrednictwem strony na której wyświetlone są zasoby (W przypadku googla jesteśmy pytani np: o dostęp klienta do naszego profilu, zdjęć itp).
  3. W przypadku, gdy właściciel zasobu udziela dostępu, serwer autoryzacji przekierowuje user-agenta z powrotem przy użyciu linka przekierowującego podanego wcześniej w parametrze zapytania: redirect_uri. Identyfikator URI przekierowania zawiera w parametrze kod autoryzacji  i stan podany przez klienta w pierwszym kroku.
  4. Klient żąda tokena dostępu z punktu końcowego serwera autoryzacji, dołączając kod autoryzacji otrzymany w poprzednim kroku. Klient uwierzytelnia się również za pomocą serwera autoryzacji. W celu zweryfikowania żądanie zawiera również identyfikator URI przekierowania używany do uzyskania kodu autoryzacji.
  5. Serwer autoryzacji uwierzytelnia klienta, weryfikuje kod autoryzacji i zapewnia, że odebrany link przekierowujący  jest zgodny z linkiem przekierowującym użytym do przekierowania klienta w trzecim kroku. Jeśli jest prawidłowy, serwer autoryzacji odpowiada tokenem dostępu i opcjonalnie tokenem odświeżania

Authorization Code zapewnia bardzo wysoki poziom bezpieczeństwa, ponieważ

  1. poświadczenia właściciela zasobu nigdy nie są ujawniane klientowi,
  2. jest to proces oparty na przekierowaniu,
  3. klient uwierzytelnia się na serwerze zasobów
  4. token dostępu jest przesyłane bezpośrednio do klienta.
Implicit Grant:

Jest uproszczoną wersją Authorization Code, w której klientowi wydaje się token dostępu bezpośrednio za pośrednictwem autoryzacji właściciela, zamiast wydawania nowego żądania przy użyciu kodu autoryzacji.

response type = token

Implicity Grant

  1. Klient inicjuje proces i kieruje użytkownika  przez user agent do serwera autoryzacyjnego. Żądanie zawiera identyfikator klienta, zakres, stan lokalny, który zostaje  zachowany, oraz link przekierowujący, do którego serwer autoryzacji odeśle użytkownika klienta po przyznaniu dostępu.
  2. Serwer autoryzacji uwierzytelnia właściciela zasobu za pośrednictwem klienta. Właściciel zasobu następnie przyznaje lub odrzuca żądanie dostępu klienta, zwykle za pośrednictwem strony z żądanymi dostępami do zasobów
  3. W przypadku, gdy właściciel zasobu udziela dostępu, serwer autoryzacji kieruje go z powrotem do user agenta za pomocą linka przekwaterowującego. Jednocześnie do fragmentu URL doklejany jest token dostępu.
  4. User agent postępuje zgodnie z instrukcjami przekierowania i wysyła żądanie do zasobu klienta hostowanego w Internecie (strona internetowa). Zazwyczaj jest to strona HTML ze skryptem do wyodrębnienia tokena z linka
  5. Strona internetowa wykonuje skrypt i wyodrębnia token dostępu i zwraca do user agenta
  6. User agent ostatecznie przekazuje token dostępu do klienta

Implicit Grant: jest zoptymalizowany pod klientów publicznych, którzy zazwyczaj działają w przeglądarce jak pełne aplikacje Javascript. Nie ma osobnego żądania otrzymania tokena dostępu, co czyni go nieco bardziej responsywnym i wydajnym dla takiego rodzaju klientów. Z drugiej strony nie obejmuje uwierzytelniania klienta, a token dostępu jest widoczny bezpośrednio w linku.

Resource Owner Password Credentials:

grant_type = password

Resource Owner Password Credentials

Jest bardzo uproszczonym, bezkierunkowym procesem, w którym właściciel zasobów podaje klientowi swoją nazwę użytkownika i hasło, a sam klient używa ich do bezpośredniego pobrania tokena dostępu z serwera autoryzacji.

  1. Właściciel zasobu podaje klientowi swoją nazwę użytkownika i hasło.
  2. Klient żąda tokena dostępu od serwera autoryzacji, podając poświadczenia podane przez właściciela zasobu. Podczas żądania klient uwierzytelnia się na serwerze autoryzacji.
  3. Serwer autoryzacji uwierzytelnia klienta i sprawdza poświadczenia właściciela zasobu. Jeśli wszystkie są prawidłowe, wydaje token dostępu.

Resource Owner Password Credentials jest odpowiedni tylko dla zaufanych klientów oraz gdy inne typy nie są dostępne (np. Nie można używać tego typu dla klienta przeglądarkowego).

Client Credentials Grant:

jest uproszczonym typem, który działa całkowicie bez właściciela zasobu (można powiedzieć, że to klient JEST właścicielem zasobu).

grant_type = client_credentials

Client Credentials Grant

  1. Klient uwierzytelnia się na serwerze autoryzacji i żąda tokena dostępu.
  2. Serwer autoryzacji uwierzytelnia klienta i jeśli jest ok, wydaje token dostępu

Jest powszechnie używany, gdzie klient działa we własnym imieniu. Bardzo częstym przypadkiem użycia jest komunikacja między wewnętrznymi mikro-usługami. Klient MUSI być także klientem poufnym.

Typy tokenów:

W poprzednich akapitach wspomniałem o dwóch rodzajach tokenów jak „token dostępu” (access token)  i „token odświerzający” (refresh token). Token odświeżający może być zwrócony tylko przez proces typu Authorization Code i  Resource Owner Password Credentials. Pewnie się zastanawiasz, jaka jest pomiędzy nimi różnica:

  • Token dostępu służy do uzyskiwania dostępu do chronionych zasobów i reprezentuje autoryzację wydaną klientowi. Zastępuje różne konstrukcje autoryzacji (np. Nazwę użytkownika i hasło) pojedynczym tokenem zrozumiałym dla serwera zasobów.
  • Token odświeżania, który jest również wysyłany do klienta przez serwer autoryzacji, służy do uzyskania nowego tokena dostępu, gdy bieżący token stanie się nieważny lub wygaśnie. Jeśli serwer autoryzacji wyda token odświeżania, jest on uwzględniany podczas wydawania tokena dostępu. Token odświeżania może być używany tylko przez serwer autoryzacji.

OpenID Connect:

Opisując OAuth2.0 powiedziałem wcześniej, że jego celem jest wydanie tokenu dostępu do chronionych zasobów. Innymi słowy OAuth2.0 zapewnia nam za pomocą OAuth2.0 tylko autoryzację. Nie dostarcza nam uwierzytelnienia. Rzeczywisty użytkownik nigdy nie jest uwierzytelniany bezpośrednio w kliencie. Tokeny dostępowe udostępniają nam tylko pseoudo-uwierzytelnienie bez wpływu na tożsamość. Pseudo-uwierzytelnienie nie zapewnia informacji o tym, kiedy, gdzie i jak nastąpiło uwierzytelnienie. I właśnie w tym miejscu do gry wchodzi OpenID.

OpenID connect to tzw. warstwa tożsamości w protokole OAuth2.0. Umożliwia klientom weryfikację tożsamości użytkownika końcowego na podstawie uwierzytelnienia wykonywanego przez serwer autoryzacji. Uzyskuje podstawowe informacje o profilu użytkownika w sposób podobny do REST. Wykorzystuje oświadczenia do przekazywania informacji o użytkowniku i rozszerza protokół OAuth.

Ponizej podstawowi aktorzy w OpenID connect:

  • End-User: Odpowiednik użytkownika, w OAuth2.0 nazywamy go właścicielem zasobów
  • Relying Party: w OAuth2.0 jest to aplikacja kliencka. Wymaga do działania uwierzytelnionego End-Usera i jego claimsów
  • Identity Provider: w OAuth2.0 jest to serwer autoryzacyjny który uwierzytelnia End-Usera i przekazuje jego claimsy,
  • Identity Token: jest to JWT zawierający claimsy użytkownika, które to posiadają informacje o uwierzytelnieniu.

Ponieważ OpenID Connect jest oparty na OAuth 2.0, wykorzystuje tylko niektóre procesy OAuth 2.0. W rzeczywistości OpenID Connect może śledzić proces autoryzacji, Implicit i Hybrid, który jest kombinacją dwóch poprzednich. Procesy są dokładnie takie same, z tą różnicą, że identyfikator jest wydawany razem z kluczem dostępu. To, czy proces jest czystym OAuth 2.0, czy OpenID Connect, zależy od obecności w żądaniu autoryzacji zakresu „openid”.

Token tożsamości & JWT

Token tożsamości zawiera informacje o uwierzytelnieniu i jest zwracany jako JSON, którego wszyscy znają jako JWT czyli JSON Web Token. JWT jest to otwarty standard prezentowania claims-ów, które można bezpiecznie przenosić pomiędzy aplikacjami. Bezpiecznie, to znaczy, że są podpisane cyfrowo, że dane są zaufane i że nie ma żadnych zmian w trakcie przesyłania pomiędzy aplikacjami. JWT mogą być wysyłane poprzez URL żądania POST lub nagłówka HTTP. Są sprawdzane lokalnie przez serwery zasobów przy użyciu klucza serwera autoryzacji. Ważne aby pamiętać o tym, że token jest wysyłany z serwera autoryzacji i normalnie, gdy zostanie wysłany do serwera zasobów, będzie musiał odesłać go z powrotem do serwera autoryzacji w celu weryfikacji!

Claimsy i zakresy:

Ostatnio często wspominałem o jakiś claims-ach, teraz przyszedł czas na szybkie wyjaśnienie. Claimsy są to informacje w postaci klucz-wartość na temat naszego użytkownika. Zakresy służą do pobierania całych zestawów claims-ów. Podstawowym i obligatoryjnym zakresem jest właśnie „OpenId”. W próżniejszych częściach zauważysz, że każde requesty wysyłane w procesie będą zawierały właśnie ten zakres. Standardowe zakresy możesz znaleźć pod tym linkiem OpenId claims

I tyle z teorii. W następnej części zabieram się za implementację.

Poniżej kilka linków,które mogą być pomocne:

  • Dokumentacja oAuth2.0 – link
  • OpenID Connect – link
  • IdentityServer 4 – link

 

Wzorce projektowe Strategia (Strategy)

We wpisie postaram się wyjaśnić i zastosować w praktyce jeden z najpopularniejszych wzorców projektowych Startegia (Strategy )  czasami także znana pod nazwą Policy. Wzorzec strategia ze względu na wersalność jest używany w większości projektów nad którymi pracowałem. W telegraficznym skrócie wzorzec umożliwia nam wykorzystanie różnych algorytmów do wykonania tej samej rzeczy w zależności od kontekstu w jakim będzie wykorzystany.


Poniżej kilka podpunktów definiujących nam wzorzec strategia (Strategy):

  • Pozwala nam zdefiniować grupę algorytmów, w której w każdym z elementów możemy zhermetyzować indywidualną implementację algorytmu i wybrać odpowiedni dynamicznie
  • Wzorzec pozwala nam na wykonanie różnych algorytmów rozwiązujących ten sam problem niezależnie od klienta, który z niej korzysta.
  • Strategia przechowuje abstrakcję wykonania algorytmu w interfejsie, a klasy implementujące ją szczegóły jego realizacji

W skrócie wzorzec strategia (Strategy) umożliwia klientowi rozwiązanie problemu poprzez wybranie sposobu jego rozwiązania ad-hoc bez znajomości szczegółów rozwiązania.

Jak już jesteśmy na bardzo formalnym etapie poniżej diagramik UML obrazujący wzorzec:

Wzorzec strategia (Strategy)
Diagram UML wzorzec strategia (Strategy)

Co tutaj mamy:

  • Client wykonawca algorytmu
  • Interfejs IStrategy, abstrakcja naszego algorytmu, klient wie tylko o jego istnieniu, i tylko z niego korzysta do wykonania algorytmu,
  • Klasy StrategyA i StrategyB, które implementują powyższy interfejs, są to konkretne wersje naszego algorytmu

Kiedy z niej najlepiej skorzystać z wzorca strategia (Strategy)?

  • Wtedy, gdy mamy w powiązanych ze sobą klasach niewielkie różnice w logice,
  • Potrzebujemy kilka wersji algorytmu,
  • Wykorzystywane algorytmy potrzebują dostępu do danych, których nie należy udostępniać klientowi,
  • Chcemy aby zachowanie naszego kodu było zdefiniowane podczas jego użytkowania,
  • Mamy dużo instrukcji warunkowych Case lub If

Przykład wykorzystania:

Obliczanie podatku od wynagrodzenia

Obecnie w przypadku umowy o pracę mamy dwa sposoby rozliczenia z podatku dochodowego. Poprzez skalę i poprzez podatek liniowy. Co z tym idzie mamy drobne różnice w sposobie implementacji algorytmu do obliczenia podatku dochodowego. Klient korzystający z naszej funkcjonalności chce mieć tylko możliwość wyboru sposobu opodatkowania w trakcie działania programu. Nic prostszego, powyższy kontekst idealnie pasuje nam do wzorca strategii (Strategy). Potrzebujemy jedynie interfejsu, dwóch implementacji algorytmu i wola gotowe. To zabieramy się do kodu.

Zacznijmy od zdefiniowania abstrakcji naszego algorytmu (na diagramie UML interfejsu IStrategy)
using SF.Domain.DTO;

namespace SF.Domain.TaxCalculators
{
    public interface ITaxCalculator
    {
        decimal Calculate(TaxCalculationContext context);
    }
}

Zdefiniowaliśmy sobie interfejs ITaxCalculators który posiada tylko jedną metodę Calculate wykorzystywaną do obliczenia podatku. To z tego interfejsu będzie korzystał nasz klient, i to będzie tylko jedno miejsce jego styku z naszymi algorytmami liczącymi podatek dochodowy.

Zabieramy się do implementacji naszych algorytmów, zgodnie z wymaganiami potrzebujemy tylko dwóch. Jednego do obliczeń podatków ze skali podatkowej i drugiego liniowego.

Implementacja dla podatku liniowego 😉
public class LinearCalculatorStrategy : ITaxCalculatorStrategy

    {
        private readonly ITaxPercentagesService _taxPercentagesService;

        public LinearCalculatorStrategy(ITaxPercentagesService taxPercentagesService)
        {
            _taxPercentagesService = taxPercentagesService;
        }


        public decimal Calculate(TaxCalculationContext context)
        {
            if (context == null) throw new ArgumentNullException(nameof(context));

            var percentage = GetTaxPercentage();
            return context.CurrentIncome * percentage;
        }

        private decimal GetTaxPercentage()
        {
            IncomeTaxThreshold incomeTaxThreshold = _taxPercentagesService.GetLinearRate() ?? throw new DomainException("Tax percentages not found");

            return incomeTaxThreshold.Percentage;
        }
    }

Co tutaj mamy. Nasza klasa LinearCalculatorStrategy implementuje naszą abstrakcję algorytmu w tym przypadku ITaxCalculatorStrategy. W konstruktorze wstrzykujemy serwis, który zwróci nam aktualne oprocentowanie w przypadku podatku liniowego. No kwintesencje algorytmu sposób obliczenia naszego podatku 😉

Pozostał drugi algorytm dla skali podatkowej:
public class GeneralCalculatorStrategy : ITaxCalculatorStrategy
    {
        private readonly ITaxPercentagesService _taxPercentagesService;

        public GeneralCalculatorStrategy(ITaxPercentagesService taxPercentagesService)
        {
            _taxPercentagesService = taxPercentagesService ?? throw new ArgumentNullException(nameof(taxPercentagesService));
        }
        public decimal Calculate(TaxCalculationContext context)
        {
            if (context == null) throw new ArgumentNullException(nameof(context));
            decimal taxValue = 0;
            List incomeTaxThresholds = GetIncomeTaxThreshold();

            var currentValueIncomeTaxThreshold =
                GetCurrentValueIncomeTaxThreshold(context, incomeTaxThresholds);

            if (IsLimitValueMonth(currentValueIncomeTaxThreshold, context))
            {
                IncomeTaxThreshold nextThrashold = GetNextIncomeTaxThreshold(incomeTaxThresholds, currentValueIncomeTaxThreshold);
                taxValue = CalculateForBorderMonth(context, currentValueIncomeTaxThreshold, nextThrashold);
            }
            else
            {
                taxValue = CalculateForFullMonth(currentValueIncomeTaxThreshold, context);
            }

            return taxValue;
        }

        private decimal CalculateForFullMonth(IncomeTaxThreshold currentValueIncomeTaxThreshold, TaxCalculationContext context)
        {
            return context.CurrentIncome * currentValueIncomeTaxThreshold.Percentage;
        }

        private decimal CalculateForBorderMonth(TaxCalculationContext context, IncomeTaxThreshold currentValueIncomeTaxThreshold, IncomeTaxThreshold nextThrashold)
        {
            decimal taxValue = (currentValueIncomeTaxThreshold.ToAmount - context.TotalIncomes) * currentValueIncomeTaxThreshold.Percentage;
            var remainingValueForNextThreshold =((context.TotalIncomes + context.CurrentIncome) - nextThrashold.FromAmount) * nextThrashold.Percentage;

            return taxValue + remainingValueForNextThreshold;
        }

        private IncomeTaxThreshold GetNextIncomeTaxThreshold(List incomeTaxThresholds,
            IncomeTaxThreshold currenTaxThreshold)
        {
            return incomeTaxThresholds.FirstOrDefault(x => x.ThresholdNumber == (currenTaxThreshold.ThresholdNumber + 1));
        }

        private bool IsLimitValueMonth(IncomeTaxThreshold currentValueIncomeTaxThreshold, TaxCalculationContext context)
        {
            var currentIncomesSum = context.TotalIncomes + context.CurrentIncome;
            return currentIncomesSum > currentValueIncomeTaxThreshold.ToAmount
                   && context.TotalIncomes > currentValueIncomeTaxThreshold.FromAmount;
        }

        private List GetIncomeTaxThreshold()
        {
            return _taxPercentagesService.GetGeneralIncomeTaxThresholds();
        }

        private IncomeTaxThreshold GetCurrentValueIncomeTaxThreshold(TaxCalculationContext context, List incomeTaxThresholds)
        {
            var value = (context.TotalIncomes + context.CurrentIncome);
            var retval = incomeTaxThresholds.FirstOrDefault(x => x.FromAmount <= context.TotalIncomes && x.ToAmount >= value || x.ToAmount >= context.TotalIncomes);
            return retval;
        }


    }

Uff troche kodu jest. Ale spokojnie niczym nie różni się od poprzedniego 😉 Przecież oblicza tylko podatek dochodowy… Jak porzednio mamy konkretny algorytm GeneralCalculatorStrategy implementujący naszą abstrakcję algorytmu ITaxCalculatorStrategy. No i jedną metodę obliczającą podatek dochodowy (plus kilka pomocniczych 🙂 nie będę tu wnikał w szczegóły implementacji sposobu obliczenia podatku dla skali).

Ok ale brakuje nam jeszcze naszego sławnego klienta.
public class TaxCalculator : ITaxCalculator
    {
        private readonly IComponentContext _componentContext;

        public TaxCalculator(IComponentContext componentContext)
        {
            _componentContext = componentContext ?? throw new ArgumentNullException(nameof(componentContext));
        }

        decimal ITaxCalculator.Calculate(TaxCalculationContext context)
        {
            ITaxCalculatorStrategy calculator = null;
            calculator = _componentContext.ResolveKeyed(context.TaxationForm);

            if (calculator == null) throw new DomainException($"Tax calculator for {context.TaxationForm} not found");

            return calculator.Calculate(context);
        }
    }

Mamy tu klasę TaxCalculator, która reprezentuje naszego klienta oraz jego abstrakcję ITaxCalculator.  Dzięki takiemu zabiegowi operujemy na abstrakcjach zgodnie z założeniami Dependency Injection. Jeszcze jedna rzecz, warta wyjaśnienia to wykorzystanie IComponentContext. Jest to interfejs dostarczony nam przez kontener dependency injection Autofac. To za  pomocą niego możemy wybrać implementację naszego algorytmu w trakcie działania programu.

Jeszcze dla dopełnienie  przykładu rejestracja naszych elementów w autofac:
            builder.RegisterType()
                .As()
                .InstancePerLifetimeScope();

            builder.RegisterType()
                .As()
                .InstancePerLifetimeScope();

            builder.RegisterType()
                .Keyed(TaxationForm.GENERAL)
                .InstancePerLifetimeScope();

            builder.RegisterType()
                .Keyed(TaxationForm.LINEAR)
                .InstancePerLifetimeScope();

I to wszystko. Nic skomplikowanego prawda?

A teraz dodatek.

Dzięki wykorzystaniu wzorca strategii w naszym zadaniu spełniliśmy kilka zasad SOLID 😉

  1. Single Rsponsibiliti Principle: każda zaimplementowana przez nas klasa odpowiada za wykonanie tylko jednej rzeczy. Najlepszy przykład to nasze algorytmy.
  2. Open/Close Principle: Zawsze możemy dodać nowa implementację algorytmu, lub w łatwy sposób zastąpić istniejący bez impaktu na pozostałe elementy naszej aplikacji.
  3. Dependency inversion Principle: Nasze komponenty wyższego rzędu jak TaxCalculator nie są zależne od komponentów niższego rzędu oraz wszystko zależnie jest od abstrakcji a nie od konkretnej implementacji.

Pełną implementację przykładu można zobaczyć w repo : https://github.com/erloon/SF.git