30 lis 2025·4 min czytania

Strażnicy routingu w Vue 3 dla dostępu opartego na rolach: praktyczne wzorce

Strażnicy routingu w Vue 3 dla kontroli dostępu opartego na rolach wyjaśnieni przy użyciu praktycznych wzorców: reguły w `meta` trasy, bezpieczne przekierowania, przyjazne fallbacki 401/403 oraz sposoby unikania wycieków danych.

Strażnicy routingu w Vue 3 dla dostępu opartego na rolach: praktyczne wzorce

Co faktycznie rozwiązują strażnicy tras (a czego nie rozwiązują)

Strażnicy tras robią jedną rzecz dobrze: kontrolują nawigację. Decydują, czy ktoś może wejść na daną trasę i gdzie go wysłać, jeśli nie może. To poprawia UX, ale nie zastępuje bezpieczeństwa.

Ukrycie elementu menu to tylko wskazówka, nie autoryzacja. Ludzie wciąż mogą wpisać URL, odświeżyć stronę z głębokim linkiem lub otworzyć zakładkę. Jeśli jedyną ochroną jest „przycisk nie jest widoczny”, nie masz ochrony.

Strażnicy sprawdzają się, gdy chcesz, aby aplikacja zachowywała się spójnie, blokując strony, które nie powinny być widoczne — np. obszary admina, narzędzia wewnętrzne czy portale klientów oparte na rolach.

Strażnicy pomagają Ci:

  • Zablokować strony przed renderowaniem
  • Przekierować do logowania lub bezpiecznego domyślnego widoku
  • Pokazać czytelną stronę 401/403 zamiast uszkodzonego widoku
  • Unikać przypadkowych pętli nawigacyjnych

Czego strażnicy nie potrafią zrobić sami: chronić danych. Jeśli API zwraca wrażliwe dane do przeglądarki, użytkownik nadal może wywołać to endpoint bezpośrednio (albo przejrzeć odpowiedzi w narzędziach deweloperskich), nawet jeśli strona jest zablokowana. Prawdziwa autoryzacja musi działać również po stronie serwera.

Dobrym celem jest zabezpieczenie obu stron: blokowanie stron i blokowanie danych. Jeśli agent wsparcia otworzy trasę dostępną tylko dla administratora, strażnik powinien zatrzymać nawigację i wyświetlić „Dostęp zabroniony”. Równolegle backend powinien odmawiać wywołań API dostępnych tylko dla adminów, żeby wrażliwe dane nigdy nie zostały zwrócone.

Wybierz prosty model ról i uprawnień

Kontrola dostępu robi się skomplikowana, gdy zaczynasz od długiej listy ról. Zacznij od niewielkiego zestawu, który ludzie naprawdę rozumieją, i dodawaj bardziej szczegółowe uprawnienia dopiero wtedy, gdy poczujesz rzeczywisty ból.

Praktyczny podział to:

  • Role opisują, kim ktoś jest w aplikacji.
  • Uprawnienia opisują, co może robić.

Dla większości narzędzi wewnętrznych trzy role wystarczają na początek:

  • admin: zarządza użytkownikami i ustawieniami, widzi wszystkie dane
  • support: obsługuje rekordy klientów i odpowiedzi, ale nie ustawia systemu
  • viewer: dostęp tylko do odczytu do zatwierdzonych ekranów

Zdecyduj wcześnie, skąd pochodzą role. Claimy w tokenie (JWT) są szybkie dla strażników, ale mogą być nieaktualne, dopóki token nie zostanie odświeżony. Pobranie profilu użytkownika przy starcie aplikacji daje zawsze aktualne dane, ale strażnicy muszą poczekać, aż to żądanie się zakończy.

Wyraźnie rozdziel typy tras: publiczne (dostępne dla wszystkich), wymagające uwierzytelnienia (wymagają sesji) i zastrzeżone (wymagają roli lub uprawnienia).

Definiuj reguły dostępu za pomocą meta trasy

Najczystszy sposób wyrażenia dostępu to zadeklarowanie go bezpośrednio przy trasie. Vue Router pozwala dołączyć obiekt meta do rekordu trasy, aby strażnicy mogli go później odczytać. Dzięki temu reguły są blisko stron, które chronią.

Wybierz prosty kształt meta i trzymaj się go w całej aplikacji.

const routes = [
  {
    path: "/admin",
    component: () => import("@/pages/AdminLayout.vue"),
    meta: { requiresAuth: true, roles: ["admin"] },
    children: [
      {
        path: "users",
        component: () => import("@/pages/AdminUsers.vue"),
        // inherits requiresAuth + roles from parent
      },
      {
        path: "audit",
        component: () => import("@/pages/AdminAudit.vue"),
        meta: { permissions: ["audit:read"] },
      },
    ],
  },
  {
    path: "/tickets",
    component: () => import("@/pages/Tickets.vue"),
    meta: { requiresAuth: true, permissions: ["tickets:read"], readOnly: true },
  },
]

Dla tras zagnieżdżonych zdecyduj, jak reguły się łączą. W większości aplikacji dzieci powinny dziedziczyć wymagania rodzica. W strażniku sprawdzaj każdy dopasowany rekord trasy (nie tylko to.meta), żeby nie pominąć wymagań rodzica.

Jedna rzecz, która oszczędzi czasu później: rozróżnij „może zobaczyć” i „może edytować”. Trasa może być widoczna dla supportu i adminów, ale edycje powinny być zablokowane dla supportu. Flaga readOnly: true w meta może sterować zachowaniem UI (wyciszać akcje, ukrywać przyciski destrukcyjne) bez udawania, że to jest zabezpieczenie.

Przygotuj stan auth, żeby strażnicy zachowywali się przewidywalnie

Większość błędów ze strażnikami wynika z jednego problemu: strażnik uruchamia się zanim aplikacja pozna użytkownika.

Traktuj auth jak mały automat stanów i zrób z niego pojedyncze źródło prawdy. Chcesz mieć trzy jasne stany:

  • unknown: aplikacja właśnie wystartowała, sesja nie została jeszcze sprawdzona
  • logged out: sprawdzenie sesji zakończone, brak ważnego użytkownika
  • logged in: użytkownik załadowany, role/uprawnienia dostępne

Zasada: nigdy nie czytaj ról, gdy auth jest unknown. To właśnie powoduje błyski chronionych ekranów lub niespodziewane przekierowania do logowania.

Zdecyduj, jak działa odświeżanie sesji

Wybierz jedną strategię odświeżania i trzymaj się jej (np. odczytaj token, wywołaj endpoint „who am I”, ustaw użytkownika).

Stabilny wzorzec wygląda tak:

  • Przy starcie aplikacji ustaw auth na unknown i rozpocznij pojedyncze żądanie odświeżające
  • Rozwiązuj strażników dopiero po zakończeniu (lub wygaśnięciu) tego żądania
  • Cache'uj użytkownika w pamięci, nie w meta trasy
  • W razie błędu ustaw auth na logged out
  • Udostępnij obietnicę ready (lub podobny mechanizm), na którą strażnicy mogą czekać

Po wdrożeniu tego wzorca logika strażnika pozostaje prosta: poczekaj, aż auth będzie gotowy, a potem podejmij decyzję o dostępie.

Krok po kroku: implementacja autoryzacji na poziomie trasy

Rozszerz kontrolę dostępu na mobile
Twórz natywne aplikacje iOS i Android z tymi samymi regułami ról stojącymi za Twoimi API.
Buduj mobilnie

Czyste podejście to trzymać większość reguł w jednym globalnym strażniku, a używać per-trasa beforeEnter tylko wtedy, gdy trasa naprawdę potrzebuje specjalnej logiki.

1) Dodaj globalny guard beforeEach

// router/index.js
router.beforeEach(async (to) => {
  const auth = useAuthStore()

  // Step 2: wait for auth initialization when needed
  if (!auth.ready) await auth.init()

  // Step 3: check authentication, then roles/permissions
  if (to.meta.requiresAuth && !auth.isAuthenticated) {
    return { name: 'login', query: { redirect: to.fullPath } }
  }

  const roles = to.meta.roles
  if (roles && roles.length > 0 && !roles.includes(auth.userRole)) {
    return { name: 'forbidden' } // 403
  }

  // Step 4: allow navigation
  return true
})

To pokrywa większość przypadków bez rozrzucania sprawdzeń po komponentach.

Kiedy lepszy jest beforeEnter

Używaj beforeEnter, gdy reguła jest naprawdę specyficzna dla trasy, np. „tylko właściciel zgłoszenia może otworzyć tę stronę” i zależy od to.params.id. Trzymaj go krótko i korzystaj z tego samego store auth, żeby zachowanie było spójne.

Bezpieczne przekierowania bez otwierania luk

Wdróż do chmury
Uruchom na AppMaster Cloud, AWS, Azure lub Google Cloud, kiedy będziesz gotowy.
Wdróż aplikację

Przekierowania mogą cicho podważyć Twoją kontrolę dostępu, jeśli traktujesz je jako zaufane.

Częsty wzorzec: gdy użytkownik jest wylogowany, wysyłasz go do logowania i dołączasz parametr query returnTo. Po zalogowaniu odczytujesz go i nawigujesz tam. Ryzyko to open redirects (wysyłanie użytkowników poza aplikację) i pętle.

Trzymaj zachowanie proste:

  • Wylogowani użytkownicy idą do Login z returnTo ustawionym na bieżącą ścieżkę.
  • Zalogowani, lecz nieuprawnieni użytkownicy idą na dedykowaną stronę Forbidden (nie na Login).
  • Pozwalaj tylko na wewnętrzne wartości returnTo, które rozpoznajesz.
  • Dodaj jedną kontrolę pętli, żeby nigdy nie przekierować w kółko.
const allowedReturnTo = (to) => {
  if (!to || typeof to !== 'string') return null
  if (!to.startsWith('/')) return null
  // optional: only allow known prefixes
  if (!['/app', '/admin', '/tickets'].some(p => to.startsWith(p))) return null
  return to
}

router.beforeEach((to) => {
  if (!auth.isReady) return false

  if (!auth.isLoggedIn && to.name !== 'Login') {
    return { name: 'Login', query: { returnTo: to.fullPath } }
  }

  if (auth.isLoggedIn && !canAccess(to, auth.user) && to.name !== 'Forbidden') {
    return { name: 'Forbidden' }
  }
})

Unikaj wycieku zastrzeżonych danych podczas nawigacji

Najprostszy wyciek to ładowanie danych zanim wiesz, czy użytkownik ma do nich dostęp.

W Vue często zdarza się to, gdy strona pobiera dane w setup(), a strażnik wykona się chwilę później. Nawet jeśli użytkownik zostanie przekierowany, odpowiedź może trafić do współdzielonego store lub pokazać się przez moment na ekranie.

Bezpieczniejsza zasada: autoryzuj najpierw, potem ładuj.

// router guard: authorize before entering the route
router.beforeEach(async (to) => {
  await auth.ready() // ensure roles are known
  const required = to.meta.requiredRole
  if (required && !auth.hasRole(required)) {
    return { name: 'forbidden' }
  }
})

Uważaj też na opóźnione żądania, gdy nawigacja zmienia się szybko. Anuluj żądania (np. przez AbortController) lub ignoruj późne odpowiedzi, sprawdzając identyfikator żądania.

Cache to kolejna pułapka. Jeśli przechowujesz „ostatnio załadowany rekord klienta” globalnie, odpowiedź dostępna tylko dla admina może później być pokazana zwykłemu użytkownikowi, który odwiedzi ten sam szablon ekranu. Klucze cache'uj według id użytkownika i roli oraz czyść wrażliwe moduły przy wylogowaniu (lub gdy role się zmieniają).

Kilka praktyk zapobiegających większości wycieków:

  • Nie pobieraj wrażliwych danych, dopóki autoryzacja nie zostanie potwierdzona.
  • Keyuj cache według użytkownika i roli lub trzymaj dane lokalnie na stronie.
  • Anuluj lub ignoruj żądania w locie przy zmianie trasy.

Przyjazne fallbacki: 401, 403 i nie znaleziono

Stwórz przyjazne strony awaryjne
Szybko zbuduj strony 401, 403 i 404 przy pomocy narzędzi UI AppMaster.
Buduj UI

Ścieżki „nie” są tak samo ważne jak ścieżki „tak”. Dobre strony awaryjne utrzymują orientację użytkownika i zmniejszają liczbę zgłoszeń do wsparcia.

401: Wymagane logowanie (nie uwierzytelniony)

Używaj 401, gdy użytkownik nie jest zalogowany. Komunikat trzymaj prosty: musi się zalogować, aby kontynuować. Jeśli wspierasz powrót na oryginalną stronę po logowaniu, waliduj ścieżkę powrotu, aby nie mogła wskazywać poza aplikację.

403: Brak dostępu (uwierzytelniony, ale nieuprawniony)

Używaj 403, gdy użytkownik jest zalogowany, ale nie ma uprawnień. Zachowaj komunikat neutralny i nie sugeruj wrażliwych szczegółów.

Solidna strona 403 zwykle ma czytelny tytuł („Dostęp zabroniony”), jedno zdanie wyjaśnienia i bezpieczny następny krok (powrót do pulpitu, kontakt z administratorem, zmiana konta jeśli dostępne).

404: Nie znaleziono

Obsługuj 404 oddzielnie od 401/403. W przeciwnym razie ludzie będą myśleć, że brakuje im uprawnień, gdy strona po prostu nie istnieje.

Typowe błędy, które łamią kontrolę dostępu

Większość błędów kontroli dostępu to proste pomyłki logiczne, które objawiają się pętlami przekierowań, błyskami niewłaściwych ekranów lub zablokowaniem użytkownika.

Zwykłe przyczyny:

  • Traktowanie ukrytego UI jako „bezpieczeństwa”. Zawsze egzekwuj role w routerze i w API.
  • Odczytywanie ról ze starego stanu po wylogowaniu/logowaniu.
  • Przekierowywanie nieuprawnionych użytkowników na inną chronioną trasę (natychmiastowa pętla).
  • Ignorowanie momentu „auth się jeszcze ładuje” przy odświeżeniu.
  • Mylące 401 i 403, co dezorientuje użytkowników.

Realistyczny przykład: agent wsparcia wylogowuje się, a na tym samym współdzielonym komputerze loguje się administrator. Jeśli strażnik odczyta zcache'owaną rolę zanim nowa sesja zostanie potwierdzona, możesz błędnie zablokować admina lub — co gorsza — chwilowo pozwolić na dostęp, którego nie powinien mieć.

Szybka lista kontrolna przed wydaniem

Skonfiguruj logowanie w poprawny sposób
Użyj modułów auth AppMaster, aby zarządzać sesjami i profilami użytkowników w czysty sposób.
Skonfiguruj uwierzytelnianie

Zrób krótkie przejście skupione na chwilach, w których kontrola dostępu zwykle zawodzi: wolne sieci, wygasłe sesje i zapisane adresy URL.

  • Każda chroniona trasa ma jawne wymagania w meta.
  • Strażnicy obsługują stan ładowania auth bez błysków chronionego UI.
  • Nieuprawnieni użytkownicy trafiają na czytelny ekran 403 (nie mylący powrót do strony głównej).
  • Każde przekierowanie „powrót do” jest walidowane i nie tworzy pętli.
  • Wrażliwe wywołania API uruchamiane są dopiero po potwierdzonej autoryzacji.

Następnie przetestuj jeden scenariusz end-to-end: otwórz chroniony URL w nowej karcie będąc wylogowanym, zaloguj się jako zwykły użytkownik i potwierdź, że trafiasz na stronę, do której masz dostęp, albo na czytelny 403 z instrukcją dalszego kroku.

Przykład: dostęp supportu vs admina w małej aplikacji webowej

Prototypuj zasady strażników
Zaprojektuj role i uprawnienia, a potem przetestuj deep linki i przekierowania w jednym projekcie.
Wypróbuj AppMaster

Wyobraź sobie aplikację helpdesk z dwiema rolami: support i admin. Support może czytać i odpowiadać na zgłoszenia. Admin może robić to samo, plus zarządzać billingiem i ustawieniami firmy.

  • /tickets/:id jest dostępne dla support i admin
  • /settings/billing jest dostępne tylko dla admin

Typowy scenariusz: agent supportu otwiera stary deep link do /settings/billing z zakładki. Strażnik powinien sprawdzić meta trasy zanim komponent się załaduje i zablokować nawigację. Ponieważ użytkownik jest zalogowany, ale nie ma roli, powinien trafić na bezpieczny fallback (403).

Dwa komunikaty mają znaczenie:

  • Wymagane logowanie (401): „Zaloguj się, aby kontynuować.”
  • Dostęp zabroniony (403): „Nie masz dostępu do ustawień rozliczeń.”

Co nie może się wydarzyć: komponent billing mountuje się lub dane billingowe są pobrane, nawet przez chwilę.

Zmiany ról w trakcie sesji to kolejny przypadek brzegowy. Jeśli ktoś zostanie awansowany lub zdegradowany, nie polegaj na samym menu. Ponownie sprawdzaj role przy nawigacji i zdecyduj, jak obsłużysz aktywne strony: odśwież stan auth przy zmianie profilu lub wykrywaj zmiany ról i przekierowuj z stron, które nie są już dozwolone.

Następne kroki: utrzymuj reguły dostępu w porządku

Gdy strażnicy działają, większym ryzykiem jest dryf: nowa trasa wypuszczona bez meta, rola zmieniona nazwa i reguły stają się niespójne.

Zamień swoje reguły w mały plan testów, który uruchomisz za każdym razem, gdy dodajesz trasę:

  • Jako Gość: otwórz chronione trasy i potwierdź, że trafiasz na login bez widocznej częściowej zawartości.
  • Jako Użytkownik: otwórz stronę, do której nie powinieneś mieć dostępu i potwierdź czytelne 403.
  • Jako Admin: przetestuj deep linki kopiowane z paska adresu.
  • Dla każdej roli: odśwież na chronionej trasie i potwierdź stabilny rezultat.

Jeśli chcesz dodatkowe zabezpieczenie, dodaj widok developerski lub output w konsoli, który wypisuje trasy i ich wymagania meta, dzięki czemu brakujące reguły będą od razu widoczne.

Jeżeli budujesz narzędzia wewnętrzne lub portale z AppMaster (appmaster.io), możesz zastosować to samo podejście: trzymaj strażników skupionych na nawigacji w UI Vue3 i egzekwuj uprawnienia tam, gdzie leży logika backendu i dane.

Wybierz jedną rzecz do poprawy i wdroż ją end-to-end: zaostrz bramkowanie pobierania danych, ulepsz stronę 403 albo zablokuj obsługę przekierowań. Małe poprawki to te, które zatrzymują większość realnych błędów dostępu.

FAQ

Czy strażnicy routingu to faktyczne zabezpieczenia, czy tylko UX?

Strażnicy routingu kontrolują nawigację, a nie dostęp do danych. Pomagają zablokować stronę, przekierować i wyświetlić czytelny stan 401/403, ale nie powstrzymają kogoś przed bezpośrednim wywołaniem API. Zawsze egzekwuj te same uprawnienia po stronie serwera, aby wrażliwe dane nigdy nie były zwracane.

Dlaczego samo ukrycie elementu menu nie wystarcza do kontroli dostępu opartej na rolach?

Ukrywanie elementu interfejsu zmienia tylko to, co ktoś widzi, a nie to, co może zażądać. Użytkownicy wciąż mogą wpisać adres URL, otworzyć zakładkę lub użyć deep linku. Musisz mieć sprawdzenia w routerze, by zablokować stronę, oraz autoryzację po stronie serwera, by zablokować dane.

Jaki prosty model ról i uprawnień warto zacząć stosować?

Zacznij od małego, zrozumiałego zestawu ról, a dopiero potem dodawaj uprawnienia, gdy poczujesz ból. Typowy zestaw to admin, support i viewer, a potem dodajesz uprawnienia jak tickets:read czy audit:read dla konkretnych akcji. Rozdziel „kim jesteś” (rola) od „co możesz zrobić” (uprawnienie).

Jak używać Vue Router `meta` do kontroli dostępu?

Umieszczaj reguły dostępu w meta rekordów routingu, np. requiresAuth, roles i permissions. Dzięki temu zasady są blisko stron, które chronią, i globalny guard działa przewidywalnie. Dla zagnieżdżonych tras sprawdzaj wszystkie dopasowane rekordy, żeby nie pominąć wymagań rodzica.

Jak obsłużyć zagnieżdżone trasy, aby dzieci dziedziczyły ograniczenia rodzica?

Czytaj to.matched i łącz wymagania ze wszystkich dopasowanych rekordów routingu. W ten sposób trasa potomna nie ominie requiresAuth lub roles ustawionego przez rodzica. Ustal wcześniej jasną regułę łączenia (zwykle: wymagania rodzica obowiązują dla dzieci).

Jak zatrzymać pętle przekierowań i wyświetlanie chronionych stron podczas odświeżania?

Częstym powodem „flashy” lub pętli przekierowań jest to, że guard działa zanim aplikacja pozna użytkownika. Traktuj auth jako trzy stany — unknown, logged out, logged in — i nigdy nie oceniaj ról, gdy auth jest unknown. Spraw, by guard czekał na inicjalizację (np. jedno żądanie „who am I”) przed podjęciem decyzji.

Kiedy używać globalnego `beforeEach`, a kiedy `beforeEnter`?

Domyślnie używaj globalnego beforeEach dla reguł typu „wymaga logowania” lub „wymaga roli/uprawnienia”. beforeEnter stosuj tylko wtedy, gdy reguła jest naprawdę specyficzna dla trasy i zależy od parametrów, np. „tylko właściciel zgłoszenia może otworzyć tę stronę”. Oba mechanizmy korzystają z tego samego źródła prawdy o auth.

Jak robić przekierowania po logowaniu bez otwierania dziur typu open redirect?

Traktuj returnTo jako niezaufane wejście. Pozwalaj tylko na wewnętrzne ścieżki, które rozpoznajesz (np. zaczynające się od / i pasujące do znanych prefiksów) i dodaj kontrolę pętli, żeby nie przekierować z powrotem na tę samą zablokowaną trasę. Użytkownicy niezalogowani idą do Login; zalogowani, lecz nieuprawnieni — na dedykowaną stronę 403.

Jak unikać wycieków danych podczas nawigacji?

Autoryzuj zanim pobierzesz. Jeśli strona robi fetch w setup() i chwilę później następuje przekierowanie, odpowiedź może trafić do globalnego store lub chwilowo pojawić się na ekranie. Odmierz wrażliwe żądania dopiero po potwierdzonej autoryzacji i anuluj albo ignoruj przeterminowane odpowiedzi przy zmianie trasy.

Jaka jest poprawna różnica między 401, 403 i 404 w aplikacji Vue?

Używaj 401, gdy użytkownik nie jest zalogowany, 403 gdy jest zalogowany, ale nie ma uprawnień. Traktuj 404 oddzielnie, by użytkownicy nie mylili braku strony z brakiem dostępu. Jasne i konsekwentne fallbacky zmniejszają liczbę zgłoszeń do działu wsparcia.

Łatwy do uruchomienia
Stworzyć coś niesamowitego

Eksperymentuj z AppMaster z darmowym planem.
Kiedy będziesz gotowy, możesz wybrać odpowiednią subskrypcję.

Rozpocznij