authn_z
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
\section{Authentikáció}
|
||||
|
||||
\begin{frame}{Mi az authentikáció?}
|
||||
\begin{block}{Definíció}
|
||||
Folyamat, amely során a rendszer ellenőrzi egy felhasználó identitását.
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Kérdés:} Ki vagy?
|
||||
\item \textbf{Cél:} Felhasználó identitásának megállapítása
|
||||
\item \textbf{Eredmény:} Sikeres vagy sikertelen azonosítás
|
||||
\end{itemize}
|
||||
|
||||
\begin{alertblock}{Fontos!}
|
||||
Authentikáció $\neq$ Autorizáció!
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Authentikáció vs. Autorizáció}
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{block}{Authentikáció}
|
||||
\begin{itemize}
|
||||
\item Ki vagy?
|
||||
\item Bejelentkezés
|
||||
\item Példa: Jelszó ellenőrzés
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{block}{Autorizáció}
|
||||
\begin{itemize}
|
||||
\item Mit tehetsz?
|
||||
\item Jogosultságok
|
||||
\item Példa: Admin jogosultság
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
|
||||
\begin{center}
|
||||
\texttt{Authentikáció} $\rightarrow$ \texttt{Autorizáció}
|
||||
\end{center}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Authentikációs módszerek}
|
||||
\begin{enumerate}
|
||||
\item \textbf{Tudás alapú}
|
||||
\begin{itemize}
|
||||
\item Példa: jelszó, PIN kód
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Tulajdon alapú}
|
||||
\begin{itemize}
|
||||
\item Példa: okostelefon, hardver token
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Inherencia alapú}
|
||||
\begin{itemize}
|
||||
\item Példa: ujjlenyomat, arcfelismerés
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Többfaktoros authentikáció (MFA)}
|
||||
\begin{block}{Multi-Factor Authentication}
|
||||
Két vagy több független faktor kombinációja.
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{2FA példák:}
|
||||
\begin{itemize}
|
||||
\item Jelszó + SMS kód
|
||||
\item Jelszó + Authenticator app
|
||||
\item Jelszó + ujjlenyomat
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Előnyök:}
|
||||
\begin{itemize}
|
||||
\item Magasabb biztonság
|
||||
\item Védelem jelszó kompromittálódás esetén
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Jelszó alapú authentikáció}
|
||||
\begin{block}{Leggyakoribb módszer}
|
||||
Felhasználónév + jelszó párossal történő azonosítás.
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Folyamat:}
|
||||
\begin{enumerate}
|
||||
\item Felhasználó megadja username + password
|
||||
\item Rendszer összehasonlítja a hash-sel
|
||||
\item Sikeres/sikertelen válasz
|
||||
\end{enumerate}
|
||||
|
||||
\item \textbf{Biztonság:}
|
||||
\begin{itemize}
|
||||
\item Soha ne tároljuk plain text-ben!
|
||||
\item Erős hash (bcrypt, Argon2)
|
||||
\item Salt minden jelszóhoz
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Token alapú authentikáció}
|
||||
\begin{block}{Modern megközelítés}
|
||||
Bejelentkezés után szerver tokent generál, kliens minden kéréshez csatolja.
|
||||
\end{block}
|
||||
|
||||
\begin{enumerate}
|
||||
\item Bejelentkezés (username + password)
|
||||
\item Szerver ellenőrzi
|
||||
\item Token generálás
|
||||
\item Kliens eltárolja (localStorage, cookie)
|
||||
\item Token csatolása minden kéréshez
|
||||
\item Szerver validálja
|
||||
\end{enumerate}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Típusok:} JWT, OAuth, API keys
|
||||
\item \textbf{Előnyök:} Stateless, skálázható
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Session alapú authentikáció}
|
||||
\begin{block}{Hagyományos megközelítés}
|
||||
Bejelentkezési információkat a szerver tárolja session-ökben.
|
||||
\end{block}
|
||||
|
||||
\begin{enumerate}
|
||||
\item Bejelentkezés
|
||||
\item Szerver session létrehozás
|
||||
\item Session ID küldése cookie-ban
|
||||
\item Kliens csatolja minden kéréshez
|
||||
\item Szerver azonosítja a felhasználót
|
||||
\end{enumerate}
|
||||
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Előnyök:}
|
||||
\begin{itemize}
|
||||
\item Biztonságosabb
|
||||
\item Könnyű visszavonás
|
||||
\end{itemize}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Hátrányok:}
|
||||
\begin{itemize}
|
||||
\item Stateful
|
||||
\item Nehezebb skálázás
|
||||
\end{itemize}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{OAuth 2.0 / OpenID Connect}
|
||||
\begin{block}{Harmadik fél authentikáció}
|
||||
Bejelentkezés külső szolgáltatókkal (Google, Facebook, GitHub).
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{OAuth 2.0:} Autorizációs framework
|
||||
\item \textbf{OpenID Connect:} Authentikációs réteg
|
||||
|
||||
\item \textbf{Előnyök:}
|
||||
\begin{itemize}
|
||||
\item Nincs jelszó kezelés
|
||||
\item Jobb UX
|
||||
\item Biztonságos protokoll
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Használat:}
|
||||
\begin{itemize}
|
||||
\item Social login
|
||||
\item SSO megoldások
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=5]{Biztonsági megfontolások}
|
||||
\begin{alertblock}{Gyakori sebezhetőségek}
|
||||
\begin{itemize}
|
||||
\item Gyenge jelszavak
|
||||
\item Nincs rate limiting
|
||||
\item Token kiszivárogtatás
|
||||
\item Man-in-the-middle (nincs HTTPS)
|
||||
\end{itemize}
|
||||
\end{alertblock}
|
||||
|
||||
\begin{block}{Best Practices}
|
||||
\begin{itemize}
|
||||
\item HTTPS mindig
|
||||
\item Erős jelszó policy
|
||||
\item Rate limiting
|
||||
\item MFA
|
||||
\item Token lejárati idő
|
||||
\item CSRF védelem
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
Binary file not shown.
@@ -0,0 +1,25 @@
|
||||
\documentclass[usenames,dvipsnames,aspectratio=169]{beamer}
|
||||
\usepackage{../common/webfejl}
|
||||
|
||||
\title[Webtechnológia és webalkalmazás-fejlesztés - Auth]{Webtechnológia és webalkalmazás-fejlesztés - Auth}
|
||||
\subtitle{Authentikáció és autorizáció}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\begin{frame}[plain]
|
||||
\titlepage
|
||||
\logoalul
|
||||
\end{frame}
|
||||
|
||||
\input{authn.tex}
|
||||
\input{authz.tex}
|
||||
\input{hash.tex}
|
||||
\input{cookies.tex}
|
||||
\input{session.tex}
|
||||
\input{jwt.tex}
|
||||
\input{oauth2.tex}
|
||||
\input{middlewares.tex}
|
||||
\input{services.tex}
|
||||
|
||||
|
||||
\end{document}
|
||||
@@ -0,0 +1,191 @@
|
||||
\section{Autorizáció}
|
||||
|
||||
\begin{frame}{Mi az autorizáció?}
|
||||
\begin{block}{Definíció}
|
||||
Meghatározza, hogy egy azonosított felhasználó milyen erőforrásokhoz férhet hozzá és milyen műveleteket végezhet.
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Kérdés:} Mit tehetsz?
|
||||
\item \textbf{Cél:} Jogosultságok szabályozása
|
||||
\item \textbf{Eredmény:} Engedélyezett vagy tiltott művelet
|
||||
\end{itemize}
|
||||
|
||||
\begin{alertblock}{Fontos!}
|
||||
Autorizáció az authentikáció után történik.
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Autorizációs modelljei}
|
||||
\begin{enumerate}
|
||||
\item \textbf{RBAC (Role-Based Access Control)}
|
||||
\begin{itemize}
|
||||
\item Szerepkörök alapján
|
||||
\item Példa: Admin, Editor, User
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{ABAC (Attribute-Based)}
|
||||
\begin{itemize}
|
||||
\item Attribútum alapján (user, resource, context)
|
||||
\item Példa: "Department = IT és 9-17 között"
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{ACL (Access Control List)}
|
||||
\begin{itemize}
|
||||
\item Erőforrás-szintű lista
|
||||
\item Példa: fájlrendszer jogosultságok
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{RBAC - Szerepkör alapú}
|
||||
\begin{block}{Lényege}
|
||||
Felhasználók szerepköröket kapnak, ez határozza meg a jogosultságokat.
|
||||
\end{block}
|
||||
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Előnyök:}
|
||||
\begin{itemize}
|
||||
\item Könnyű adminisztráció
|
||||
\item Átlátható
|
||||
\item Gyors ellenőrzés
|
||||
\end{itemize}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Hátrányok:}
|
||||
\begin{itemize}
|
||||
\item Skálázhatóság
|
||||
\item Kevesebb rugalmasság
|
||||
\end{itemize}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{ABAC - Attribútum alapú}
|
||||
\begin{block}{Lényege}
|
||||
Hozzáférési szabályok attribútumok alapján.
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{User:} szerepkör, osztály, beosztás
|
||||
\item \textbf{Resource:} tulajdonos, típus
|
||||
\item \textbf{Context:} idő, hely, eszköz
|
||||
\end{itemize}
|
||||
|
||||
\begin{exampleblock}{Példa szabály}
|
||||
\texttt{(role=Manager) AND (resource.owner = user) AND (time < 18:00)}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{ACL - Access Control List}
|
||||
\begin{block}{Lényege}
|
||||
Erőforrásonként tárolt lista a hozzáférési jogokról.
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Példa:} fájlrendszer (read, write, execute)
|
||||
\item \textbf{Előny:} finém hozzáférés
|
||||
\item \textbf{Hátrány:} nagy rendszereknelnehéz kezelni
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Autorizáció webalkalm azásban}
|
||||
\begin{block}{Tipikus folyamat}
|
||||
Szerver minden kérésnél ellenőrzi a jogosultságot.
|
||||
\end{block}
|
||||
|
||||
\begin{enumerate}
|
||||
\item Authnentikáció után szerepkör kiosztás
|
||||
\item Tokenben vagy session-ben tárolás
|
||||
\item Middleware ellenőrzi a hozzáférést
|
||||
\item Nincs jog: \texttt{403 Forbidden}
|
||||
\end{enumerate}
|
||||
|
||||
\begin{alertblock}{HTTP státuszkód}
|
||||
\texttt{401} = nincs auth, \texttt{403} = nincs jogosultság
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=10]{Autorizációs middleware (RBAC)}
|
||||
\begin{exampleblock}{Egyszerű szerepkör ellenőrzés}
|
||||
\tiny
|
||||
\begin{verbatim}
|
||||
const authorize = (...roles) => {
|
||||
return (req, res, next) => {
|
||||
const { role } = req.user; // pl. JWT-ből
|
||||
if (!roles.includes(role)) {
|
||||
return res.status(403).json({ error: 'Forbidden' });
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
app.get('/admin', auth, authorize('Admin'), (req, res) => {
|
||||
res.json({ data: 'Admin content' });
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=10]{Policy alapú autorizáció (ABAC)}
|
||||
\begin{exampleblock}{Szabály központú hozzáférés}
|
||||
\tiny
|
||||
\begin{verbatim}
|
||||
const canAccess = (user, resource) => {
|
||||
return user.department === resource.department &&
|
||||
user.level >= resource.requiredLevel &&
|
||||
new Date().getHours() < 18;
|
||||
};
|
||||
|
||||
app.get('/reports/:id', auth, async (req, res) => {
|
||||
const report = await getReport(req.params.id);
|
||||
if (!canAccess(req.user, report)) {
|
||||
return res.status(403).json({ error: 'Forbidden' });
|
||||
}
|
||||
res.json(report);
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Legkisebb jogosultság elve}
|
||||
\begin{block}{Principle of Least Privilege}
|
||||
Mindenki csak a feladatohoz szükséges minimális jogokat kapja.
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item Csökkenti a kockázatot
|
||||
\item Korlátozza a hibák hatását
|
||||
\item Egyszerűbb auditálás
|
||||
\end{itemize}
|
||||
|
||||
\begin{alertblock}{Gyakori hiba}
|
||||
\textbf{Mindenki admin} = gyors fejlesztés, de veszélyes élesben!
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Autorizáció Best Practices}
|
||||
\begin{itemize}
|
||||
\item Minden érzékeny végpontot ellenőrizz
|
||||
\item Middleware vagy policy réteg
|
||||
\item Role/policy központi kezelés
|
||||
\item Logolás
|
||||
\item Admin funkciók külön kezelése
|
||||
\item RBAC + ABAC kombináció
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Összefoglalás - Autorizáció}
|
||||
\begin{itemize}
|
||||
\item Autorizáció = hozzáférések és jogosultságok kezelése
|
||||
\item 401 vs 403: authentikáció hiánya vs jogosultság hiánya
|
||||
\item Fő modellek: RBAC, ABAC, ACL
|
||||
\item Middleware-ekkel megvalósítható a gyakorlatban
|
||||
\item Least Privilege elv alkalmazása
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.5cm}
|
||||
|
||||
\end{frame}
|
||||
@@ -0,0 +1,166 @@
|
||||
\section{HTTP Cookies}
|
||||
|
||||
\begin{frame}[shrink=15]{Mi az a Cookie?}
|
||||
\begin{block}{HTTP Cookie definíció}
|
||||
Kis méretű adat (max 4KB), amit szerver küld böngészőnek, automatikusan visszaküldődik.
|
||||
\end{block}
|
||||
\begin{itemize}
|
||||
\item Session kezelés, preferenciák, tracking
|
||||
\item Beállítható élettartam
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=15]{Cookie beállítása Express-ben}
|
||||
\begin{verbatim}
|
||||
// Egyszerű cookie
|
||||
res.cookie('session', 'abc123');
|
||||
|
||||
// Opciókkal
|
||||
res.cookie('user', 'john', {
|
||||
maxAge: 900000, // 15 perc ms-ban
|
||||
httpOnly: true, // JS nem fér hozzá
|
||||
secure: true, // csak HTTPS
|
||||
sameSite: 'strict' // CSRF védelem
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{Cookie attribútumok}
|
||||
\begin{small}
|
||||
\begin{itemize}
|
||||
\item \textbf{Domain}: \texttt{.example.com} - subdomain-ek
|
||||
\item \textbf{Path}: \texttt{/api} - URL path scope
|
||||
\item \textbf{Expires/Max-Age}: Lejárat ideje
|
||||
\item \textbf{HttpOnly}: JS hozzáférés blokkolása (XSS védelem)
|
||||
\item \textbf{Secure}: Csak HTTPS-en küldve
|
||||
\item \textbf{SameSite}: \texttt{Strict|Lax|None} - CSRF védelem
|
||||
\end{itemize}
|
||||
\end{small}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{Cookie típusok}
|
||||
\begin{enumerate}
|
||||
\item \textbf{Session Cookie}: Nincs Expires/Max-Age, böngésző bezáráskor törlődik
|
||||
\item \textbf{Persistent Cookie}: Van lejárati idő, túléli böngésző bezárást
|
||||
\item \textbf{Secure Cookie}: Csak HTTPS-en
|
||||
\item \textbf{HttpOnly Cookie}: JS nem fér hozzá
|
||||
\item \textbf{SameSite Cookie}: CSRF támadás ellen
|
||||
\item \textbf{Third-party Cookie}: Más domain-ről
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{SameSite Cookie attribútum}
|
||||
\begin{description}
|
||||
\item[Strict] Cookie csak saját site kérésekkel küldve. Legjobb CSRF védelem.
|
||||
\item[Lax] GET kéréseknél küldi cross-site. Default modern böngészőkben.
|
||||
\item[None] Mindig küldi (Secure kötelező). Third-party használathoz.
|
||||
\end{description}
|
||||
\begin{alertblock}{CSRF véd}
|
||||
SameSite=Strict/Lax megakadályozza cross-site támadásokat.
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{Cookie vs localStorage vs sessionStorage}
|
||||
\begin{tiny}
|
||||
\begin{tabular}{|l|c|c|c|}
|
||||
\hline
|
||||
& \textbf{Cookie} & \textbf{localStorage} & \textbf{sessionStorage} \\
|
||||
\hline
|
||||
Kapacitás & 4KB & 5-10MB & 5-10MB \\
|
||||
Lejárat & Beállítható & Nincs & Tab bezárás \\
|
||||
HTTP-ben & Igen & Nem & Nem \\
|
||||
XSS véd & HttpOnly & Nem & Nem \\
|
||||
\hline
|
||||
\end{tabular}
|
||||
\end{tiny}
|
||||
\begin{exampleblock}{Ajánlás}
|
||||
\textbf{Érzékeny:} HttpOnly Cookie, \textbf{Publikus:} localStorage
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=15]{Cookie olvasása Express-ben}
|
||||
\begin{verbatim}
|
||||
const cookieParser = require('cookie-parser');
|
||||
app.use(cookieParser());
|
||||
|
||||
app.get('/profile', (req, res) => {
|
||||
const sessionId = req.cookies.session;
|
||||
res.json({ sessionId });
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=15]{Cookie törlése}
|
||||
\begin{verbatim}
|
||||
app.post('/logout', (req, res) => {
|
||||
res.clearCookie('session');
|
||||
res.json({ message: 'Logged out' });
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=15]{Signed Cookies}
|
||||
\begin{block}{Integritás védelem}
|
||||
HMAC aláírással ellátott cookie, megakadályozza manipulációt.
|
||||
\end{block}
|
||||
\begin{verbatim}
|
||||
app.use(cookieParser('secret-key'));
|
||||
|
||||
app.get('/set', (req, res) => {
|
||||
res.cookie('userId', '123', { signed: true });
|
||||
res.send('OK');
|
||||
});
|
||||
|
||||
app.get('/get', (req, res) => {
|
||||
const id = req.signedCookies.userId;
|
||||
res.json({ id });
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{XSS vs CSRF}
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{XSS (Cross-Site Scripting)}
|
||||
\begin{itemize}
|
||||
\item JS injection támadás
|
||||
\item Cookie lopás \texttt{document.cookie}
|
||||
\item \textbf{Védelem:} HttpOnly cookie
|
||||
\end{itemize}
|
||||
\end{column}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{CSRF (Cross-Site Request Forgery)}
|
||||
\begin{itemize}
|
||||
\item Hamis kérés küldés
|
||||
\item Cookie auto-send kihasználása
|
||||
\item \textbf{Védelem:} SameSite, CSRF token
|
||||
\end{itemize}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{CSRF Token védelem}
|
||||
\begin{verbatim}
|
||||
const csrf = require('csurf');
|
||||
app.use(csrf({ cookie: true }));
|
||||
|
||||
app.get('/form', (req, res) => {
|
||||
res.render('form', { csrfToken: req.csrfToken() });
|
||||
});
|
||||
|
||||
app.post('/submit', (req, res) => {
|
||||
res.send('Valid!');
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{Cookie Security Best Practices}
|
||||
\begin{enumerate}
|
||||
\item \textbf{HttpOnly}: Érzékeny cookie-khoz mindig
|
||||
\item \textbf{Secure}: Éles környezetben HTTPS-el
|
||||
\item \textbf{SameSite}: Strict vagy Lax CSRF ellen
|
||||
\item \textbf{Short expiration}: Session token-ekhez rövid lejárat
|
||||
\item \textbf{Domain scope}: Csak szükséges domain-hez
|
||||
\item \textbf{Signed cookies}: Integritás ellenőrzéshez
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
@@ -0,0 +1,470 @@
|
||||
\section{HTTP Cookies}
|
||||
|
||||
\begin{frame}{Mi az a Cookie?}
|
||||
\begin{block}{HTTP Cookie definíció}
|
||||
Kis méretű adat, amit a szerver küld a böngészőnek, és automatikusan vissza küldődik.
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Méret:} Max 4KB
|
||||
\item \textbf{Tárolás:} Böngésző
|
||||
\item \textbf{Élettartam:} Beállítható
|
||||
|
||||
\item \textbf{Használat:}
|
||||
\begin{itemize}
|
||||
\item Session kezelés
|
||||
\item Felhasználói preferenciák
|
||||
\item Tracking
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
|
||||
\begin{exampleblock}{Authentikációban}
|
||||
Cookie-k ideálisak session ID és token tárolására.
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Cookie beállítása}
|
||||
\begin{block}{Set-Cookie HTTP header}
|
||||
A szerver a \texttt{Set-Cookie} header segítségével küldi a cookie-t a kliensnek.
|
||||
\end{block}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{HTTP válasz:}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
HTTP/1.1 200 OK
|
||||
Set-Cookie: sessionId=abc123
|
||||
Set-Cookie: theme=dark
|
||||
Content-Type: application/json
|
||||
|
||||
{"success": true}
|
||||
\end{verbatim}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Node.js/Express:}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
res.cookie('sessionId', 'abc123');
|
||||
|
||||
res.cookie('theme', 'dark', {
|
||||
maxAge: 900000,
|
||||
httpOnly: true
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
|
||||
\vspace{0.5cm}
|
||||
|
||||
\begin{itemize}
|
||||
\item A böngésző automatikusan eltárolja a cookie-t
|
||||
\item Minden későbbi kérésben a böngésző automatikusan visszaküldi
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Cookie olvasása}
|
||||
\begin{block}{Cookie HTTP header}
|
||||
A böngésző minden kérésben visszaküldi a releváns cookie-kat.
|
||||
\end{block}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{HTTP kérés:}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
GET /api/user HTTP/1.1
|
||||
Host: example.com
|
||||
Cookie: sessionId=abc123; theme=dark
|
||||
\end{verbatim}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Node.js/Express:}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
// cookie-parser middleware
|
||||
const cookieParser = require('cookie-parser');
|
||||
app.use(cookieParser());
|
||||
|
||||
// Cookie olvasás
|
||||
app.get('/api/user', (req, res) => {
|
||||
const sessionId = req.cookies.sessionId;
|
||||
const theme = req.cookies.theme;
|
||||
|
||||
// ...
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Cookie típusok}
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{block}{Session Cookie}
|
||||
\begin{itemize}
|
||||
\item Nincs Expires/Max-Age
|
||||
\item Böngésző bezárásakor törlődik
|
||||
\end{itemize}
|
||||
|
||||
\textbf{Használat:}
|
||||
\begin{itemize}
|
||||
\item Ideiglenes session
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{block}{Persistent Cookie}
|
||||
\begin{itemize}
|
||||
\item Van Expires/Max-Age
|
||||
\item Megmarad újraindítás után
|
||||
\end{itemize}
|
||||
|
||||
\textbf{Használat:}
|
||||
\begin{itemize}
|
||||
\item "Remember me"
|
||||
\item Preferenciák
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
|
||||
\begin{exampleblock}{Példa}
|
||||
\texttt{Set-Cookie: sessionId=abc123; Max-Age=3600}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=5]{Cookie attribútumok}
|
||||
\begin{enumerate}
|
||||
\item \textbf{Expires / Max-Age}
|
||||
\begin{itemize}
|
||||
\item Expires: Dátum, Max-Age: mper
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Domain}
|
||||
\begin{itemize}
|
||||
\item Mely domain-ekhez küldje
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Path}
|
||||
\begin{itemize}
|
||||
\item Mely URL path-okhoz
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Secure}
|
||||
\begin{itemize}
|
||||
\item Csak HTTPS-en
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Biztonsági attribútumok}
|
||||
\begin{alertblock}{HttpOnly}
|
||||
\begin{itemize}
|
||||
\item JavaScript NEM férhet hozzá
|
||||
\item \textbf{XSS védelem!}
|
||||
\end{itemize}
|
||||
\end{alertblock}
|
||||
|
||||
\begin{alertblock}{Secure}
|
||||
\begin{itemize}
|
||||
\item Csak HTTPS-en
|
||||
\item \textbf{MITM védelem!}
|
||||
\end{itemize}
|
||||
\end{alertblock}
|
||||
|
||||
\begin{alertblock}{SameSite}
|
||||
\begin{itemize}
|
||||
\item Strict - Csak same-site
|
||||
\item Lax - GET same-site
|
||||
\item None - Mindenhol (Secure kötelező!)
|
||||
\item \textbf{CSRF védelem!}
|
||||
\end{itemize}
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Biztonságos cookie beállítás}
|
||||
\begin{block}{Production környezetben ajánlott beállítások}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
res.cookie('token', jwtToken, {
|
||||
httpOnly: true, // XSS védelem - JS nem férhet hozzá
|
||||
secure: true, // Csak HTTPS - production-ben!
|
||||
sameSite: 'strict', // CSRF védelem
|
||||
maxAge: 3600000, // 1 óra (milliszekundumban)
|
||||
path: '/', // Teljes alkalmazásban elérhető
|
||||
domain: '.example.com' // Domain és aldomain-ek
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
|
||||
\vspace{0.5cm}
|
||||
|
||||
\begin{alertblock}{Kritikus!}
|
||||
Authentikációs token-ek mindig legyenek \texttt{httpOnly}, \texttt{secure} és \texttt{sameSite} attribútumokkal ellátva!
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=10]{SameSite részletesen}
|
||||
\begin{block}{CSRF védelem cookie szinten}
|
||||
SameSite határozza meg, cookie mikor küldhető cross-site kérésekben.
|
||||
\end{block}
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{Strict}
|
||||
\begin{itemize}
|
||||
\item Csak same-site
|
||||
\item Legjobb védelem
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Lax}
|
||||
\begin{itemize}
|
||||
\item Top-level GET küldődik
|
||||
\item POST/PUT/DELETE nem
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{None}
|
||||
\begin{itemize}
|
||||
\item Mindenhol
|
||||
\item Securekel kötelező!
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{SameSite példák}
|
||||
\begin{exampleblock}{Példa forgatókönyv}
|
||||
\texttt{bank.com} oldalon be vagy jelentkezve cookie-val.
|
||||
Megnyitsz egy \texttt{evil.com} oldalt, ami próbál kérést küldeni \texttt{bank.com}-ra.
|
||||
\end{exampleblock}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{SameSite=Strict:}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
Set-Cookie: session=xyz;
|
||||
SameSite=Strict
|
||||
\end{verbatim}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textcolor{red}{$\times$} evil.com $\to$ bank.com POST
|
||||
\item \textcolor{red}{$\times$} evil.com link $\to$ bank.com
|
||||
\item \textcolor{green}{$\checkmark$} bank.com $\to$ bank.com
|
||||
\end{itemize}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{SameSite=Lax:}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
Set-Cookie: session=xyz;
|
||||
SameSite=Lax
|
||||
\end{verbatim}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textcolor{red}{$\times$} evil.com $\to$ bank.com POST
|
||||
\item \textcolor{green}{$\checkmark$} evil.com link $\to$ bank.com GET
|
||||
\item \textcolor{green}{$\checkmark$} bank.com $\to$ bank.com
|
||||
\end{itemize}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=10]{Cookie törlése}
|
||||
\begin{block}{Logout - cookie invalidálás}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
app.post('/api/logout', (req, res) => {
|
||||
res.clearCookie('token', {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'strict'
|
||||
});
|
||||
res.json({ message: 'Logged out' });
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=10]{Cookie biztonság - XSS vs CSRF}
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{alertblock}{XSS}
|
||||
\textbf{Támadás:}
|
||||
\begin{itemize}
|
||||
\item JS injektálás
|
||||
\item Cookie ellopás
|
||||
\end{itemize}
|
||||
|
||||
\textbf{Védelem:}
|
||||
\begin{itemize}
|
||||
\item HttpOnly
|
||||
\item Input validáció
|
||||
\item CSP
|
||||
\end{itemize}
|
||||
\end{alertblock}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{alertblock}{CSRF}
|
||||
\textbf{Támadás:}
|
||||
\begin{itemize}
|
||||
\item Jogosulatlan kérés
|
||||
\item Automatikus cookie
|
||||
\end{itemize}
|
||||
|
||||
\textbf{Védelem:}
|
||||
\begin{itemize}
|
||||
\item SameSite
|
||||
\item CSRF token
|
||||
\item Origin check
|
||||
\end{itemize}
|
||||
\end{alertblock}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
|
||||
\begin{block}{Kombinált}
|
||||
HttpOnly + Secure + SameSite + CSRF token
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=10]{CSRF token}
|
||||
\begin{block}{Double submit pattern}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
const csrf = require('csurf');
|
||||
const csrfProtection = csrf({ cookie: { httpOnly: true } });
|
||||
|
||||
app.use(csrfProtection);
|
||||
|
||||
app.get('/api/csrf-token', (req, res) => {
|
||||
res.json({ csrfToken: req.csrfToken() });
|
||||
});
|
||||
|
||||
app.post('/api/transfer', (req, res) => {
|
||||
// CSRF valid automatikus
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=10]{Cookie és CORS}
|
||||
\begin{block}{Cross-Origin}
|
||||
Cookie-k alapból NEM küldődnek cross-origin-ben.
|
||||
\end{block}
|
||||
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Szerver:}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
app.use(cors({
|
||||
origin: 'https://frontend.com',
|
||||
credentials: true
|
||||
}));
|
||||
\end{verbatim}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Kliens:}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
fetch('/api/user', {
|
||||
credentials: 'include'
|
||||
})
|
||||
\end{verbatim}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
|
||||
\begin{alertblock}{Fontos!}
|
||||
credentials: true + 'include' + SameSite=None; Secure
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=10]{Cookie vs localStorage vs sessionStorage}
|
||||
\begin{small}
|
||||
\begin{tabular}{|l|c|c|c|}
|
||||
\hline
|
||||
& \textbf{Cookie} & \textbf{localStorage} & \textbf{sessionStorage} \\
|
||||
\hline
|
||||
Kapacitás & 4KB & 5-10MB & 5-10MB \\
|
||||
\hline
|
||||
Lejárat & Beállítható & Nincs & Tab bezárás \\
|
||||
\hline
|
||||
HTTP-ben & \textcolor{green}{Igen} & \textcolor{red}{Nem} & \textcolor{red}{Nem} \\
|
||||
\hline
|
||||
XSS védelem & HttpOnly & \textcolor{red}{Nem} & \textcolor{red}{Nem} \\
|
||||
\hline
|
||||
\end{tabular}
|
||||
\end{small}
|
||||
|
||||
\begin{exampleblock}{Ajánlás}
|
||||
\textbf{Érzékeny:} HttpOnly Cookie, \textbf{Publikus:} localStorage
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=10]{Signed Cookies}
|
||||
\begin{block}{Integritás védelem}
|
||||
HMAC aláírással elliatott cookie, megakadályozza manipulációt.
|
||||
\end{block}
|
||||
|
||||
\begin{verbatim}
|
||||
const cookieParser = require('cookie-parser');
|
||||
app.use(cookieParser('secret-key'));
|
||||
|
||||
app.get('/set', (req, res) => {
|
||||
res.cookie('userId', '12345', { signed: true });
|
||||
res.send('Set');
|
||||
});
|
||||
|
||||
app.get('/get', (req, res) => {
|
||||
const userId = req.signedCookies.userId;
|
||||
res.json({ userId });
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=5]{Cookie best practices}
|
||||
\begin{block}{Authentikációs cookie-k}
|
||||
\begin{enumerate}
|
||||
\item \textbf{HttpOnly:} JS nem fér hozzá
|
||||
\item \textbf{Secure:} Csak HTTPS
|
||||
\item \textbf{SameSite:} Strict/Lax
|
||||
\item \textbf{Rövid Max-Age:} 15-60 perc
|
||||
\item \textbf{Path:} Csak szükséges
|
||||
\item \textbf{Signed cookie:} Manipuláció ellen
|
||||
\end{enumerate}
|
||||
\end{block}
|
||||
|
||||
\begin{alertblock}{Production template}
|
||||
\small
|
||||
\texttt{res.cookie('token', value, \{httpOnly: true, secure: true, sameSite: 'strict', maxAge: 3600000\})}
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=5]{\u00d6sszefoglalás}
|
||||
\begin{itemize}
|
||||
\item \textbf{Cookie} = Kis adat szerver és kliens között
|
||||
\item \textbf{Típusok:} Session (ideiglenes) vs Persistent
|
||||
\item \textbf{Attribútumok:} Expires, Domain, Path, Secure, HttpOnly, SameSite
|
||||
\item textbf{HttpOnly:} XSS védelem
|
||||
\item \textbf{Secure:} HTTPS - MITM védelem
|
||||
\item \textbf{SameSite:} CSRF védelem
|
||||
\item \textbf{CORS:} credentials: true + 'include'
|
||||
\item \textbf{Biztonság:} HttpOnly + Secure + SameSite
|
||||
\end{itemize}
|
||||
|
||||
\begin{exampleblock}{Authentikációban}
|
||||
Cookie a \textbf{legbiztonságosabb} módszer token tárolására.
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
\section{Hash}
|
||||
|
||||
\begin{frame}{Mi az a hash?}
|
||||
\begin{block}{Definíció}
|
||||
Tetszőleges hosszúságú bemenetet rögzített méretű lenyomattá alakít.
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item Egyirányű függvény (nem visszafejthető)
|
||||
\item Gyorsan számolható
|
||||
\item Kis változás teljesen más hasht eredményez
|
||||
\end{itemize}
|
||||
|
||||
\begin{alertblock}{Fontos}
|
||||
Hash $\neq$ titkosítás: nem visszafejthető!
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Hash tulajdonságok}
|
||||
\begin{itemize}
|
||||
\item \textbf{Deterministic}: azonos bemenet = azonos kimenet
|
||||
\item \textbf{Preimage resistance}: nehéz visszafejteni
|
||||
\item \textbf{Second preimage}: nehéz másik bemenetet találni
|
||||
\item \textbf{Collision resistance}: nehéz két különböző bemenetet találni azonos hash-sel
|
||||
\end{itemize}
|
||||
|
||||
\begin{exampleblock}{Példa}
|
||||
\texttt{hash("jelszo") = 5f4dcc3b5aa765d61d8327deb882cf99}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Gyenge vs. erős hash}
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{block}{Gyenge hash}
|
||||
\begin{itemize}
|
||||
\item MD5, SHA1 (elavult)
|
||||
\item Gyors → brute force könnyebb
|
||||
\item Ütközések ismertek
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{block}{Erős hash}
|
||||
\begin{itemize}
|
||||
\item bcrypt, Argon2, scrypt
|
||||
\item Lassú, konfigurálható
|
||||
\item Salt + work factor támogatás
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
|
||||
\vspace{0.4cm}
|
||||
|
||||
\begin{alertblock}{Jelszavakhoz}
|
||||
Soha ne használj általános hash-t (MD5, SHA1) jelszavak tárolásához!
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Salt és Pepper}
|
||||
\begin{block}{Miért fontos?}
|
||||
\textbf{Salt:} véletlen érték, amit a jelszóhoz adunk hash előtt.
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item Védelem rainbow table ellen
|
||||
\item Minden jelszónál egyedi salt
|
||||
\item Salt-et tárolhatjuk adatbázisban
|
||||
\end{itemize}
|
||||
|
||||
\begin{exampleblock}{Pepper}
|
||||
Közös titkos kulcs, szerver konfigban tárolva.
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Password hashing algoritmusok}
|
||||
\begin{itemize}
|
||||
\item \textbf{bcrypt}
|
||||
\begin{itemize}
|
||||
\item Beépített salt és work factor
|
||||
|
||||
\end{itemize}
|
||||
\item \textbf{scrypt}
|
||||
\begin{itemize}
|
||||
\item Memória-igényes → GPU támadások ellen jobb
|
||||
\end{itemize}
|
||||
\item \textbf{Argon2}
|
||||
\begin{itemize}
|
||||
\item Modern szabvány (PHC winner)
|
||||
\item Paraméterezhető (time, memory, parallelism)
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=10]{bcrypt használat (Node.js)}
|
||||
\begin{exampleblock}{Hash készítés}
|
||||
\tiny
|
||||
\begin{verbatim}
|
||||
const bcrypt = require('bcrypt');
|
||||
const saltRounds = 12;
|
||||
|
||||
const hashPassword = async (password) => {
|
||||
const salt = await bcrypt.genSalt(saltRounds);
|
||||
return bcrypt.hash(password, salt);
|
||||
};
|
||||
\end{verbatim}
|
||||
\end{exampleblock}
|
||||
|
||||
\begin{exampleblock}{Ellenőrzés}
|
||||
\tiny
|
||||
\begin{verbatim}
|
||||
const isValid = await bcrypt.compare(password, storedHash);
|
||||
\end{verbatim}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Hash-elés felhasználása}
|
||||
\begin{itemize}
|
||||
\item Jelszó tárolás
|
||||
\item Fájl integritás ellenőrzés
|
||||
\item Digitális aláírások
|
||||
\item Cache kulcsok
|
||||
\end{itemize}
|
||||
|
||||
\begin{alertblock}{Megjegyzés}
|
||||
Authentikációban: jelszó soha ne legyen visszaolvasható.
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Hash Best Practices}
|
||||
\begin{itemize}
|
||||
\item Jelszhoz \textbf{bcrypt/Argon2/scrypt}
|
||||
\item Egyedi salt minden jelszóhoz
|
||||
\item Work factor beállítása (pl. bcrypt 10-12)
|
||||
\item Soha ne plain text
|
||||
\item Rate limiting + lockout
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Összefoglalás - Hash}
|
||||
\begin{itemize}
|
||||
\item Hash = egyirányú lenyomat
|
||||
\item Jelszavakhoz speciális algoritmus kell
|
||||
\item Salt és pepper növeli a biztonságot
|
||||
\item Argon2 a legajánlottabb modern választás
|
||||
\item Hash nem titkosítás!
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.5cm}
|
||||
|
||||
\end{frame}
|
||||
@@ -0,0 +1,194 @@
|
||||
\section{JWT}
|
||||
|
||||
\begin{frame}[shrink=15]{Mi az a JWT?}
|
||||
\begin{block}{JSON Web Token (RFC 7519)}
|
||||
Kompakt, önálló token információ biztonságos továbbításához.
|
||||
\end{block}
|
||||
\begin{itemize}
|
||||
\item Digitálisan aláírt, Base64URL kódolt
|
||||
\item Használat: authentikáció, stateless session
|
||||
\item \textbf{NEM} titkosított - payload olvasható!
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=15]{JWT formátum}
|
||||
\texttt{\textcolor{red}{HEADER}.\textcolor{purple}{PAYLOAD}.\textcolor{blue}{SIGNATURE}}
|
||||
\begin{tiny}
|
||||
\begin{verbatim}
|
||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZ
|
||||
SI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36P
|
||||
\end{verbatim}
|
||||
\end{tiny}
|
||||
\begin{itemize}
|
||||
\item \textcolor{red}{Header}: Algoritmus + token típus
|
||||
\item \textcolor{purple}{Payload}: Claims (user adatok)
|
||||
\item \textcolor{blue}{Signature}: Integritás védelem
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=15]{JWT Header \& Payload}
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Header:}
|
||||
\begin{verbatim}
|
||||
{
|
||||
"alg": "HS256",
|
||||
"typ": "JWT"
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{column}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Payload:}
|
||||
\begin{verbatim}
|
||||
{
|
||||
"sub": "1234",
|
||||
"name": "John",
|
||||
"iat": 1516239022,
|
||||
"exp": 1516242622
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\begin{alertblock}{Fontos}
|
||||
Ne tároljunk jelszót payload-ban!
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=15]{JWT Signature}
|
||||
\begin{verbatim}
|
||||
HMACSHA256(
|
||||
base64(header) + "." + base64(payload),
|
||||
secret
|
||||
)
|
||||
\end{verbatim}
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{HMAC:} Egy kulcs, gyors (HS256)
|
||||
\end{column}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{RSA:} Privát+publikus, biztonságosabb (RS256)
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{JWT generálás Node.js}
|
||||
\begin{verbatim}
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const payload = { userId: user.id, role: user.role };
|
||||
|
||||
const token = jwt.sign(
|
||||
payload,
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '1h' }
|
||||
);
|
||||
|
||||
res.json({ token });
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{JWT validálás Node.js}
|
||||
\begin{verbatim}
|
||||
const token = req.headers.authorization?.split(' ')[1];
|
||||
|
||||
if (!token) return res.status(401).json({ error: 'No token' });
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
req.user = decoded;
|
||||
next();
|
||||
} catch (err) {
|
||||
res.status(403).json({ error: 'Invalid' });
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{JWT middleware}
|
||||
\begin{verbatim}
|
||||
const authJWT = (req, res, next) => {
|
||||
const token = req.headers.authorization?.split(' ')[1];
|
||||
if (!token) return res.status(401).send('Token required');
|
||||
|
||||
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
|
||||
if (err) return res.status(403).send('Invalid');
|
||||
req.user = user;
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
app.get('/protected', authJWT, (req, res) => {
|
||||
res.json({ data: 'Secret', user: req.user });
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{JWT vs Session}
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{JWT:}
|
||||
\begin{itemize}
|
||||
\item Stateless
|
||||
\item Skálázható
|
||||
\item Cross-domain
|
||||
\item Nehéz visszavonni
|
||||
\item Token méret
|
||||
\end{itemize}
|
||||
\end{column}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Session:}
|
||||
\begin{itemize}
|
||||
\item Stateful
|
||||
\item Szerver tárolja
|
||||
\item Könnyű visszavonás
|
||||
\item Nehezebb skálázás
|
||||
\item Kis cookie
|
||||
\end{itemize}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{Access \& Refresh Token}
|
||||
\begin{block}{Két token stratégia}
|
||||
\textbf{Access token}: Rövid élet (15 perc), API hozzáférés\\
|
||||
\textbf{Refresh token}: Hosszú élet (7 nap), új access token generálás
|
||||
\end{block}
|
||||
\begin{enumerate}
|
||||
\item Login $\to$ Access + Refresh token
|
||||
\item API kérés Access token-nel
|
||||
\item Access lejár $\to$ Refresh-el új Access-t kér
|
||||
\item Refresh lejár $\to$ Újra login
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{Refresh Token példa}
|
||||
\begin{verbatim}
|
||||
app.post('/refresh', (req, res) => {
|
||||
const { refreshToken } = req.body;
|
||||
if (!refreshToken) return res.sendStatus(401);
|
||||
|
||||
jwt.verify(refreshToken, REFRESH_SECRET, (err, user) => {
|
||||
if (err) return res.sendStatus(403);
|
||||
|
||||
const accessToken = jwt.sign(
|
||||
{ userId: user.userId },
|
||||
ACCESS_SECRET,
|
||||
{ expiresIn: '15m' }
|
||||
);
|
||||
res.json({ accessToken });
|
||||
});
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{JWT Security Best Practices}
|
||||
\begin{enumerate}
|
||||
\item Rövid lejárat (15-60 perc)
|
||||
\item Erős secret kulcs (min 256 bit)
|
||||
\item HTTPS kötelező
|
||||
\item Ne tároljunk érzékeny adatot payload-ban
|
||||
\item Validáld mindig az \texttt{exp} és \texttt{iat} claim-eket
|
||||
\item Használj RS256-ot HS256 helyett production-ben
|
||||
\item Refresh token rotation
|
||||
\item Token blacklist megfontolása
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
@@ -0,0 +1,549 @@
|
||||
\section{JWT}
|
||||
|
||||
\begin{frame}{Mi az a JWT?}
|
||||
\begin{block}{JSON Web Token (RFC 7519)}
|
||||
Nyílt szabványú, kompakt és önálló módszer információ biztonságos továbbítására.
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Kompakt:} Kis méret, URL/header-ben is
|
||||
\item \textbf{Önálló:} Minden információt tartalmaz
|
||||
\item \textbf{Biztonságos:} Digitálisan aláírt
|
||||
|
||||
\item \textbf{Használat:}
|
||||
\begin{itemize}
|
||||
\item Authentikáció
|
||||
\item Információ csere
|
||||
\item Stateless session
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=10]{JWT formátum}
|
||||
\begin{block}{Három részből áll, ponttal elválasztva}
|
||||
\texttt{\textcolor{red}{HEADER}.\textcolor{purple}{PAYLOAD}.\textcolor{blue}{SIGNATURE}}
|
||||
\end{block}
|
||||
|
||||
\begin{exampleblock}{Példa JWT}
|
||||
\tiny
|
||||
\begin{verbatim}
|
||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZ
|
||||
SI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36P
|
||||
OkmLXOUqhfKJ4
|
||||
\end{verbatim}
|
||||
\end{exampleblock}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textcolor{red}{\textbf{Header:}} Token típusa és algoritmus
|
||||
\item \textcolor{purple}{\textbf{Payload:}} Claims (állítások) - felhasználói adatok
|
||||
\item \textcolor{blue}{\textbf{Signature:}} Aláírás - integritás ellenőrzés
|
||||
\end{itemize}
|
||||
|
||||
\begin{alertblock}{Fontos!}
|
||||
A JWT Base64URL kódolású, \textbf{NEM} titkosított! A payload tartalma olvasható!
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{JWT Header}
|
||||
\begin{block}{Token metaadatok}
|
||||
Token típusa és aláírási algoritmus.
|
||||
\end{block}
|
||||
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{JSON:}
|
||||
\begin{verbatim}
|
||||
{
|
||||
"alg": "HS256",
|
||||
"typ": "JWT"
|
||||
}
|
||||
\end{verbatim}
|
||||
|
||||
\begin{itemize}
|
||||
\item \texttt{alg}: Algoritmus
|
||||
\item \texttt{typ}: Token típus
|
||||
\end{itemize}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Kódolt:}
|
||||
\begin{verbatim}
|
||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
|
||||
\end{verbatim}
|
||||
|
||||
\textbf{Algoritmusok:}
|
||||
\begin{itemize}
|
||||
\item HS256 - HMAC
|
||||
\item RS256 - RSA
|
||||
\item ES256 - ECDSA
|
||||
\end{itemize}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=5]{JWT Payload (Claims)}
|
||||
\begin{block}{Felhasználói adatok és meta}
|
||||
Claims (allítoltak) a felhasználóról és tokenről.
|
||||
\end{block}
|
||||
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Registered:}
|
||||
\begin{itemize}
|
||||
\item \texttt{iss} - Kibocsátó
|
||||
\item \texttt{sub} - Tárgy/user ID
|
||||
\item \texttt{aud} - Címzett
|
||||
\item \texttt{exp} - Lejárat
|
||||
\item \texttt{iat} - Kiállítás
|
||||
\end{itemize}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Példa:}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
{
|
||||
"sub": "1234567890",
|
||||
"name": "John Doe",
|
||||
"role": "admin",
|
||||
"iat": 1516239022,
|
||||
"exp": 1516242622
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
|
||||
\begin{alertblock}{Figyelem!}
|
||||
Ne tároljunk érzékeny adatokat (jelszó) payload-ban!
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=5]{JWT Signature}
|
||||
\begin{block}{Digitális aláírás}
|
||||
Biztosítja a token integritását és hitelességét.
|
||||
\end{block}
|
||||
|
||||
\textbf{Signature (HMAC SHA256):}
|
||||
\begin{verbatim}
|
||||
HMACSHA256(
|
||||
base64UrlEncode(header) + "." + base64UrlEncode(payload),
|
||||
secret
|
||||
)
|
||||
\end{verbatim}
|
||||
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{HMAC (Symmetric):}
|
||||
\begin{itemize}
|
||||
\item Egy kulcs
|
||||
\item Gyorsabb
|
||||
\item HS256
|
||||
\end{itemize}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{RSA (Asymmetric):}
|
||||
\begin{itemize}
|
||||
\item Privát + publikus
|
||||
\item Biztonságosabb
|
||||
\item RS256
|
||||
\end{itemize}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{JWT működése}
|
||||
\begin enumerate}
|
||||
\item \textbf{Bejelentkezés:}
|
||||
\begin{itemize}
|
||||
\item Username + password
|
||||
\item Szerver validál
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Token generálás:}
|
||||
\begin{itemize}
|
||||
\item JWT létrehozás user adatokkal
|
||||
\item Aláírás titkos kulccsal
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Tárolás:}
|
||||
\begin{itemize}
|
||||
\item localStorage / cookie
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{API kérések:}
|
||||
\begin{itemize}
|
||||
\item \texttt{Authorization: Bearer <token>}
|
||||
\item Szerver validálja
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=10]{JWT generálás Node.js-ben}
|
||||
\begin{block}{jsonwebtoken könyvtár használata}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const payload = {
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
const token = jwt.sign(
|
||||
payload,
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '1h', issuer: 'myapp' }
|
||||
);
|
||||
|
||||
res.json({ token, expiresIn: 3600 });
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=10]{JWT validálás Node.js-ben}
|
||||
\begin{block}{Token ellenőrzés}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
const authHeader = req.headers.authorization;
|
||||
const token = authHeader?.split(' ')[1];
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: 'No token' });
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
req.user = decoded;
|
||||
next();
|
||||
} catch (error) {
|
||||
return res.status(403).json({ error: 'Invalid' });
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=10]{JWT middleware Express-ben}
|
||||
\begin{block}{Védett route-ok}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
const authenticateJWT = (req, res, next) => {
|
||||
const token = req.headers.authorization?.split(' ')[1];
|
||||
|
||||
if (!token) return res.status(401).json({ error: 'Token required' });
|
||||
|
||||
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
|
||||
if (err) return res.status(403).json({ error: 'Invalid token' });
|
||||
req.user = user;
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
app.get('/api/protected', authenticateJWT, (req, res) => {
|
||||
res.json({ message: 'Protected', user: req.user });
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{JWT vs Session}
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{block}{JWT (Stateless)}
|
||||
\textbf{Előnyök:}
|
||||
\begin{itemize}
|
||||
\item Skálázható
|
||||
\item Nincs szerver-oldali tárolás
|
||||
\item Cross-domain működés
|
||||
\item Mobile-friendly
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\textbf{Hátrányok:}
|
||||
\begin{itemize}
|
||||
\item Nehéz visszavonni
|
||||
\item Token méret
|
||||
\item XSS sebezhetőség (localStorage)
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{block}{Session (Stateful)}
|
||||
\textbf{Előnyök:}
|
||||
\begin{itemize}
|
||||
\item Azonnali visszavonás
|
||||
\item Kis cookie méret
|
||||
\item Biztonságosabb token tárolás
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\textbf{Hátrányok:}
|
||||
\begin{itemize}
|
||||
\item Szerver-oldali memória
|
||||
\item Nehezebb skálázás
|
||||
\item CORS problémák
|
||||
\item Session store szükséges
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
|
||||
\vspace{0.5cm}
|
||||
|
||||
\begin{alertblock}{Melyiket válasszuk?}
|
||||
Függ az alkalmazás követelményeitől: méret, skálázhatóság, biztonság, komplexitás.
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Token tárolás a kliens oldalon}
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{block}{localStorage}
|
||||
\begin{itemize}
|
||||
\item Egyszerű
|
||||
\item JS hozzáférés
|
||||
\item Megosztott
|
||||
\end{itemize}
|
||||
|
||||
\textcolor{red}{\textbf{XSS!}}
|
||||
\begin{itemize}
|
||||
\item \textcolor{red}{JS hozzáférhet}
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{block}{HttpOnly Cookie}
|
||||
\begin{itemize}
|
||||
\item JS NEM fér hozzá
|
||||
\item Automatikus
|
||||
\item XSS védett
|
||||
\end{itemize}
|
||||
|
||||
\textcolor{red}{\textbf{CSRF}}
|
||||
\begin{itemize}
|
||||
\item \textcolor{red}{Védelem kell}
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
|
||||
\begin{exampleblock}{Ajánlás}
|
||||
HttpOnly, Secure, SameSite cookie a legbiztonságosabb.
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{JWT tárolás HttpOnly Cookie-ban}
|
||||
\begin{block}{Token beállítás cookie-ban}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
// Login endpoint
|
||||
app.post('/api/login', (req, res) => {
|
||||
// ... authentikáció logika ...
|
||||
|
||||
const token = jwt.sign(payload, secret, { expiresIn: '1h' });
|
||||
|
||||
res.cookie('token', token, {
|
||||
httpOnly: true, // JavaScript nem férhet hozzá
|
||||
secure: true, // Csak HTTPS-en keresztül
|
||||
sameSite: 'strict',// CSRF védelem
|
||||
maxAge: 3600000 // 1 óra milliszekundumban
|
||||
});
|
||||
|
||||
res.json({ success: true, message: 'Logged in' });
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{JWT olvasás Cookie-ból}
|
||||
\begin{block}{Cookie-parser middleware}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
const cookieParser = require('cookie-parser');
|
||||
app.use(cookieParser());
|
||||
|
||||
const authenticateJWT = (req, res, next) => {
|
||||
// Token a cookie-ból
|
||||
const token = req.cookies.token;
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
req.user = decoded;
|
||||
next();
|
||||
} catch (error) {
|
||||
return res.status(403).json({ error: 'Invalid token' });
|
||||
}
|
||||
};
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=5]{Refresh Token stratégia}
|
||||
\begin{block}{Miért szükséges?}
|
||||
Access tokenek rövidek (15 perc). Refresh tokennel új access token kérhető.
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Access Token:}
|
||||
\begin{itemize}
|
||||
\item Rövid (15-60 perc)
|
||||
\item API kérésekhez
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Refresh Token:}
|
||||
\begin{itemize}
|
||||
\item Hosszú (7-30 nap)
|
||||
\item Új access tokenhez
|
||||
\item HttpOnly cookie-ban
|
||||
\item DB-ben tárolva
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
|
||||
\begin{alertblock}{Best Practice}
|
||||
Refresh token rotation: minden refresh-nél új refresh token is.
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Refresh Token implementáció}
|
||||
\begin{block}{Token refresh endpoint}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
app.post('/api/refresh', (req, res) => {
|
||||
const refreshToken = req.cookies.refreshToken;
|
||||
|
||||
if (!refreshToken) {
|
||||
return res.status(401).json({ error: 'No refresh token' });
|
||||
}
|
||||
|
||||
try {
|
||||
// Refresh token validálás
|
||||
const decoded = jwt.verify(refreshToken, process.env.REFRESH_SECRET);
|
||||
|
||||
// Ellenőrizzük az adatbázisban
|
||||
const storedToken = await RefreshToken.findOne({
|
||||
token: refreshToken,
|
||||
userId: decoded.userId
|
||||
});
|
||||
|
||||
if (!storedToken) {
|
||||
return res.status(403).json({ error: 'Invalid refresh token' });
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Refresh Token implementáció (folyt.)}
|
||||
\begin{block}{Új tokenek generálása}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
// Új access token
|
||||
const accessToken = jwt.sign(
|
||||
{ userId: decoded.userId, role: decoded.role },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '15m' }
|
||||
);
|
||||
|
||||
// Új refresh token (rotation)
|
||||
const newRefreshToken = jwt.sign(
|
||||
{ userId: decoded.userId },
|
||||
process.env.REFRESH_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
// Régi token törlése, új mentése az adatbázisba
|
||||
await RefreshToken.deleteOne({ token: refreshToken });
|
||||
await RefreshToken.create({ token: newRefreshToken, userId: decoded.userId });
|
||||
|
||||
res.cookie('refreshToken', newRefreshToken, { httpOnly: true, ... });
|
||||
res.json({ accessToken });
|
||||
} catch (error) {
|
||||
res.status(403).json({ error: 'Invalid refresh token' });
|
||||
}
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=5]{JWT biztonság best practices}
|
||||
\begin{alertblock}{Kritikus pontok}
|
||||
\begin{itemize}
|
||||
\item \textbf{Titkos kulcs:} Erős, random, env-ben
|
||||
\item \textbf{HTTPS:} Mindig!
|
||||
\item \textbf{Lejárat:} Max 1 óra
|
||||
\item \textbf{Érzeky adatok:} Ne JWT-ben
|
||||
\item \textbf{Algoritmus:} Ellenőrizd (ne 'none')
|
||||
\end{itemize}
|
||||
\end{alertblock}
|
||||
|
||||
\begin{block}{További ajánlások}
|
||||
\begin{itemize}
|
||||
\item HttpOnly cookie
|
||||
\item Refresh token rotation
|
||||
\item Rate limiting
|
||||
\item Aud és Iss validálás
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Gyakori JWT hibák és támadások}
|
||||
\begin{alertblock}{Gyakori sebezhetőségek}
|
||||
\begin{enumerate}
|
||||
\item \textbf{None algoritmus támadás}
|
||||
\begin{itemize}
|
||||
\item Header: \texttt{"alg": "none"} - aláírás nélküli token
|
||||
\item Védelem: Mindig ellenőrizzük az algoritmust
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Gyenge titkos kulcs}
|
||||
\begin{itemize}
|
||||
\item Rövid vagy közismert kulcs
|
||||
\item Védelem: Min. 256-bit random kulcs
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Token kiszivárogtatás}
|
||||
\begin{itemize}
|
||||
\item XSS támadás localStorage-ból
|
||||
\item Védelem: HttpOnly cookie használata
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Algoritmus konfúzió}
|
||||
\begin{itemize}
|
||||
\item RS256 publikus kulcs HMAC secret-ként használva
|
||||
\item Védelem: Explicit algoritmus megadása validáláskor
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Összefoglalás}
|
||||
\begin{itemize}
|
||||
\item \textbf{JWT} = Kompakt, önálló, biztonságos token formátum
|
||||
\item \textbf{Struktúra:} Header.Payload.Signature
|
||||
\item \textbf{Előnyök:} Stateless, skálázható, cross-domain
|
||||
\item \textbf{Claims:} Registered (exp, iat, sub) és Custom (role, email)
|
||||
\item \textbf{Tárolás:} HttpOnly cookie a legbiztonságosabb
|
||||
\item \textbf{Refresh token:} Új access token kéréshez hosszú élettartammal
|
||||
\item \textbf{Biztonság:} HTTPS, erős secret, rövid lejárat, validálás
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.5cm}
|
||||
|
||||
\begin{exampleblock}{Mikor használjuk a JWT-t?}
|
||||
\begin{itemize}
|
||||
\item RESTful API authentikáció
|
||||
\item Microservice architektúra
|
||||
\item Mobile alkalmazások
|
||||
\item Single Page Applications (SPA)
|
||||
\item Stateless session kezelés
|
||||
\end{itemize}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
@@ -0,0 +1,205 @@
|
||||
\section{Middleware}
|
||||
|
||||
\begin{frame}[shrink=15]{Mi az a middleware?}
|
||||
\begin{block}{Definíció}
|
||||
Függvény HTTP kérés-válasz között, hozzáfér \texttt{req}, \texttt{res}, \texttt{next}-hez.
|
||||
\end{block}
|
||||
\begin{itemize}
|
||||
\item Láncolható
|
||||
\item Kérés előfeldolgozás, válasz utófeldolgozás
|
||||
\item Kérés megszakítható
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{Middleware működése}
|
||||
\begin{center}
|
||||
\begin{tikzpicture}[node distance=1.4cm, auto]
|
||||
\node (browser) [rectangle, draw, text width=1.5cm, text centered] {Browser};
|
||||
\node (mw1) [rectangle, draw, right of=browser, xshift=1cm, text width=2cm, text centered] {MW 1\\Logger};
|
||||
\node (mw2) [rectangle, draw, right of=mw1, xshift=1.5cm, text width=2cm, text centered] {MW 2\\Auth};
|
||||
\node (route) [rectangle, draw, right of=mw2, xshift=1.5cm, text width=1.5cm, text centered] {Route};
|
||||
|
||||
\draw[->, thick] (browser) -- node[above] {Req} (mw1);
|
||||
\draw[->, thick] (mw1) -- node[above] {\tiny next()} (mw2);
|
||||
\draw[->, thick] (mw2) -- node[above] {\tiny next()} (route);
|
||||
\draw[->, thick, dashed] (route) -- node[below] {Res} (browser);
|
||||
\end{tikzpicture}
|
||||
\end{center}
|
||||
\begin{alertblock}{Fontos}
|
||||
\texttt{next()} nélkül kérés megáll!
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{Middleware típusok}
|
||||
\begin{enumerate}
|
||||
\item \textbf{Application-level}: \texttt{app.use()}
|
||||
\item \textbf{Router-level}: \texttt{router.use()}
|
||||
\item \textbf{Error-handling}: \texttt{(err, req, res, next)}
|
||||
\item \textbf{Built-in}: \texttt{express.json()}, \texttt{express.static()}
|
||||
\item \textbf{Third-party}: \texttt{cors}, \texttt{helmet}, \texttt{morgan}
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{Logger middleware}
|
||||
\begin{verbatim}
|
||||
const logger = (req, res, next) => {
|
||||
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
|
||||
next();
|
||||
};
|
||||
|
||||
app.use(logger);
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.json({ msg: 'Hello' });
|
||||
});
|
||||
|
||||
// Kimenet:
|
||||
// [2026-02-24T10:30:15.000Z] GET /
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{Request timing middleware}
|
||||
\begin{verbatim}
|
||||
const timer = (req, res, next) => {
|
||||
req.startTime = Date.now();
|
||||
res.on('finish', () => {
|
||||
const duration = Date.now() - req.startTime;
|
||||
console.log(`${req.method} ${req.url} - ${duration}ms`);
|
||||
});
|
||||
next();
|
||||
};
|
||||
|
||||
app.use(timer);
|
||||
|
||||
// GET /api/users - 145ms
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{Validation middleware}
|
||||
\begin{verbatim}
|
||||
const validateEmail = (req, res, next) => {
|
||||
const { email } = req.body;
|
||||
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
if (!email || !re.test(email)) {
|
||||
return res.status(400).json({ error: 'Invalid email' });
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
app.post('/register', validateEmail, (req, res) => {
|
||||
res.json({ success: true });
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{Auth middleware}
|
||||
\begin{verbatim}
|
||||
const auth = (req, res, next) => {
|
||||
const token = req.headers.authorization?.split(' ')[1];
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: 'Token required' });
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, SECRET);
|
||||
req.user = decoded;
|
||||
next();
|
||||
} catch (err) {
|
||||
res.status(403).json({ error: 'Invalid token' });
|
||||
}
|
||||
};
|
||||
|
||||
app.get('/protected', auth, (req, res) => {
|
||||
res.json({ user: req.user });
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{Role-based middleware}
|
||||
\begin{verbatim}
|
||||
const requireRole = (role) => {
|
||||
return (req, res, next) => {
|
||||
if (req.user.role !== role) {
|
||||
return res.status(403).json({ error: 'Forbidden' });
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
app.delete('/user/:id', auth, requireRole('admin'), (req, res) => {
|
||||
res.json({ deleted: true });
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{Error handling middleware}
|
||||
\begin{verbatim}
|
||||
// Mindig utolsóként!
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err.stack);
|
||||
|
||||
res.status(err.status || 500).json({
|
||||
error: err.message || 'Internal Server Error'
|
||||
});
|
||||
});
|
||||
|
||||
// Használat
|
||||
app.get('/error', (req, res, next) => {
|
||||
const err = new Error('Something broke!');
|
||||
err.status = 500;
|
||||
next(err);
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{CORS middleware}
|
||||
\begin{verbatim}
|
||||
const cors = require('cors');
|
||||
|
||||
// Minden origin engedélyezése
|
||||
app.use(cors());
|
||||
|
||||
// Specifikus beállítás
|
||||
app.use(cors({
|
||||
origin: 'https://frontend.com',
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE']
|
||||
}));
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{Built-in middleware}
|
||||
\begin{itemize}
|
||||
\item \texttt{express.json()}: JSON body parsing
|
||||
\item \texttt{express.urlencoded()}: URL-encoded body parsing
|
||||
\item \texttt{express.static()}: Static fájlok kiszolgálása
|
||||
\end{itemize}
|
||||
\begin{exampleblock}{Használat}
|
||||
\texttt{app.use(express.json());}\\
|
||||
\texttt{app.use(express.static('public'));}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{Third-party middleware}
|
||||
\begin{description}
|
||||
\item[helmet] HTTP security headers
|
||||
\item[morgan] HTTP request logger
|
||||
\item[compression] Response compression (gzip)
|
||||
\item[cookie-parser] Cookie parsing
|
||||
\item[express-session] Session kezelés
|
||||
\item[passport] Authentication
|
||||
\end{description}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{Middleware best practices}
|
||||
\begin{enumerate}
|
||||
\item Mindig hívd meg \texttt{next()}-et (vagy küldjél választ)
|
||||
\item Error middleware utolsóként
|
||||
\item Sorrendiség számít!
|
||||
\item Global middleware-ek előre (\texttt{cors}, \texttt{helmet})
|
||||
\item Route-specifikus middleware-ek közvetlenül route-hoz
|
||||
\item Async middleware-eknél használj try-catch-et
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
@@ -0,0 +1,555 @@
|
||||
\section{Middleware}
|
||||
|
||||
\begin{frame}{Mi az a middleware?}
|
||||
\begin{block}{Definíció}
|
||||
Függvény, amely HTTP kérés és válasz között fut, hozzáfér \texttt{req}, \texttt{res}, \texttt{next}-hez.
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item Láncolható függvények
|
||||
\item Kérés előfeldolgozás
|
||||
\item Válasz utófeldolgozás
|
||||
\item Kérés megszakítás
|
||||
\end{itemize}
|
||||
|
||||
\begin{exampleblock}{Express middleware}
|
||||
\texttt{function middleware(req, res, next) \{ ... \}}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=5]{Middleware működése}
|
||||
\begin{center}
|
||||
\begin{tikzpicture}[node distance=1.5cm, auto]
|
||||
\node (browser) [rectangle, draw, text width=2cm, text centered] {Browser};
|
||||
\node (mw1) [rectangle, draw, right of=browser, xshift=1.5cm, text width=2.5cm, text centered] {Middleware 1\\(Logger)};
|
||||
\node (mw2) [rectangle, draw, right of=mw1, xshift=2cm, text width=2.5cm, text centered] {Middleware 2\\(Auth)};
|
||||
\node (route) [rectangle, draw, right of=mw2, xshift=2cm, text width=2cm, text centered] {Route Handler};
|
||||
|
||||
\draw[->, thick] (browser) -- node[above] {Request} (mw1);
|
||||
\draw[->, thick] (mw1) -- node[above] {\texttt{next()}} (mw2);
|
||||
\draw[->, thick] (mw2) -- node[above] {\texttt{next()}} (route);
|
||||
\draw[->, thick, dashed] (route) -- node[below] {Response} (browser);
|
||||
\end{tikzpicture}
|
||||
\end{center}
|
||||
|
||||
\begin{alertblock}{Fontos!}
|
||||
Ha nem hívod meg a \texttt{next()}-et, a kérés megáll és nem jut el a következő middleware-hez vagy route handler-hez!
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=5]{Middleware típusok}
|
||||
\begin{enumerate}
|
||||
\item \textbf{Application-level}
|
||||
\begin{itemize}
|
||||
\item Egész app-ra
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Router-level}
|
||||
\begin{itemize}
|
||||
\item Adott routerre
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Error-handling}
|
||||
\begin{itemize}
|
||||
\item 4 param: \texttt{(err, req, res, next)}
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Built-in}
|
||||
\begin{itemize}
|
||||
\item \texttt{express.json()}
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Third-party}
|
||||
\begin{itemize}
|
||||
\item \texttt{cors}, \texttt{helmet}
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=10]{Egyszerű logger middleware}
|
||||
\begin{block}{Request logging - 1. rész}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
|
||||
const logger = (req, res, next) => {
|
||||
const timestamp = new Date().toISOString();
|
||||
console.log(`[${timestamp}] ${req.method} ${req.url}`);
|
||||
next();
|
||||
};
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=10]{Egyszerű logger middleware (folyt.)}
|
||||
\begin{block}{Request logging - 2. rész (használat)}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
app.use(logger);
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.json({ message: 'Hello World' });
|
||||
});
|
||||
|
||||
app.get('/users', (req, res) => {
|
||||
res.json({ users: ['Alice', 'Bob'] });
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
|
||||
// Kimenet:
|
||||
// [2026-02-23T10:30:15.000Z] GET /
|
||||
// [2026-02-23T10:30:20.000Z] GET /users
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=10]{Request timing middleware}
|
||||
\begin{block}{Válaszidő mérése}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
const requestTimer = (req, res, next) => {
|
||||
req.startTime = Date.now();
|
||||
|
||||
res.on('finish', () => {
|
||||
const duration = Date.now() - req.startTime;
|
||||
console.log(`${req.method} ${req.url} - ${duration}ms`);
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
app.use(requestTimer);
|
||||
|
||||
// Kimenet:
|
||||
// GET /api/users - 145ms
|
||||
// POST /api/login - 523ms
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Validation middleware}
|
||||
\begin{block}{Input validáció - 1. rész}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
// email validáló middleware
|
||||
const validateEmail = (req, res, next) => {
|
||||
const { email } = req.body;
|
||||
|
||||
if (!email) {
|
||||
return res.status(400).json({
|
||||
error: 'Email is required'
|
||||
});
|
||||
}
|
||||
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) {
|
||||
return res.status(400).json({
|
||||
error: 'Invalid email format'
|
||||
});
|
||||
}
|
||||
|
||||
next(); // Validáció sikeres, tovább
|
||||
};
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Validation middleware (folyt.)}
|
||||
\begin{block}{Input validáció - 2. rész (használat)}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
app.use(express.json()); // Body parser middleware
|
||||
|
||||
// Route-specific middleware
|
||||
app.post('/register', validateEmail, (req, res) => {
|
||||
const { email, password } = req.body;
|
||||
|
||||
// Email már validálva van a middleware által
|
||||
// Regisztráció logika...
|
||||
|
||||
res.json({ message: 'User registered', email });
|
||||
});
|
||||
|
||||
// Több middleware egyszerre
|
||||
app.post('/login',
|
||||
validateEmail,
|
||||
validatePassword, // Másik validator
|
||||
(req, res) => {
|
||||
// Login logika...
|
||||
}
|
||||
);
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Authentikációs middleware - JWT}
|
||||
\begin{block}{Token validáció - 1. rész}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const authenticateJWT = (req, res, next) => {
|
||||
// Token a Authorization header-ből
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (!authHeader) {
|
||||
return res.status(401).json({
|
||||
error: 'Access token required'
|
||||
});
|
||||
}
|
||||
|
||||
// Bearer TOKEN formátum
|
||||
const token = authHeader.split(' ')[1];
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
error: 'Token not found'
|
||||
});
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Authentikációs middleware - JWT (folyt.)}
|
||||
\begin{block}{Token validáció - 2. rész}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
// Token verify
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
|
||||
// Felhasználói adat hozzáadása a request objektumhoz
|
||||
req.user = decoded; // { userId, email, role, ... }
|
||||
|
||||
next(); // Sikeres authentikáció
|
||||
|
||||
} catch (error) {
|
||||
return res.status(403).json({
|
||||
error: 'Invalid or expired token'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Használat
|
||||
app.get('/api/profile', authenticateJWT, (req, res) => {
|
||||
// req.user elérhető itt
|
||||
res.json({ user: req.user });
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Autorizációs middleware - Role-based}
|
||||
\begin{block}{Szerepkör ellenőrzés}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
// Role-based access control middleware factory
|
||||
const requireRole = (role) => {
|
||||
return (req, res, next) => {
|
||||
// Feltételezzük, hogy az authenticateJWT már futott
|
||||
if (!req.user) {
|
||||
return res.status(401).json({
|
||||
error: 'Not authenticated'
|
||||
});
|
||||
}
|
||||
|
||||
if (req.user.role !== role) {
|
||||
return res.status(403).json({
|
||||
error: `Forbidden: ${role} role required`
|
||||
});
|
||||
}
|
||||
|
||||
next(); // Jogosultság OK
|
||||
};
|
||||
};
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Autorizációs middleware (folyt.)}
|
||||
\begin{block}{Használat szerepkör ellenőrzésre}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
// Admin-only endpoint
|
||||
app.delete('/api/users/:id',
|
||||
authenticateJWT, // 1. Authentikáció
|
||||
requireRole('admin'), // 2. Autorizáció
|
||||
(req, res) => {
|
||||
// Csak admin férhet ide
|
||||
const userId = req.params.id;
|
||||
// Delete user logika...
|
||||
res.json({ message: 'User deleted' });
|
||||
}
|
||||
);
|
||||
|
||||
// Több szerepkör támogatása
|
||||
const requireAnyRole = (...roles) => {
|
||||
return (req, res, next) => {
|
||||
if (!req.user || !roles.includes(req.user.role)) {
|
||||
return res.status(403).json({ error: 'Forbidden' });
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
app.get('/api/reports', authenticateJWT, requireAnyRole('admin', 'manager'), ...);
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Rate limiting middleware}
|
||||
\begin{block}{API hívások korlátozása}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
const rateLimit = require('express-rate-limit');
|
||||
|
||||
// Rate limiter konfiguráció
|
||||
const limiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 perc
|
||||
max: 100, // Max 100 kérés 15 percenként
|
||||
message: 'Too many requests, please try again later',
|
||||
standardHeaders: true, // RateLimit-* headers
|
||||
legacyHeaders: false
|
||||
});
|
||||
|
||||
// Application-level
|
||||
app.use('/api/', limiter);
|
||||
|
||||
// Strict limiter login-hoz
|
||||
const loginLimiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000,
|
||||
max: 5, // Max 5 login kísérlet 15 percenként
|
||||
skipSuccessfulRequests: true // Sikeres login nem számít bele
|
||||
});
|
||||
|
||||
app.post('/api/login', loginLimiter, (req, res) => { ... });
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Error handling middleware}
|
||||
\begin{block}{Központi hibakezelés}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
// Error handling middleware (4 paraméter!)
|
||||
const errorHandler = (err, req, res, next) => {
|
||||
// Logolás
|
||||
console.error('Error:', err);
|
||||
|
||||
// Custom error osztályok kezelése
|
||||
if (err.statusCode) {
|
||||
return res.status(err.statusCode).json({
|
||||
error: err.name,
|
||||
message: err.message
|
||||
});
|
||||
}
|
||||
|
||||
// Váratlan hibák
|
||||
res.status(500).json({
|
||||
error: 'InternalServerError',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
};
|
||||
|
||||
// Error handler middleware UTOLJÁRA kell regisztrálni!
|
||||
app.use(errorHandler);
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Error handling használata}
|
||||
\begin{block}{Hibák továbbítása a next()-el}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
// Async route handler
|
||||
app.get('/api/users/:id', async (req, res, next) => {
|
||||
try {
|
||||
const user = await User.findById(req.params.id);
|
||||
|
||||
if (!user) {
|
||||
const error = new Error('User not found');
|
||||
error.statusCode = 404;
|
||||
throw error; // vagy: return next(error);
|
||||
}
|
||||
|
||||
res.json({ user });
|
||||
|
||||
} catch (error) {
|
||||
next(error); // Error handler middleware-nek továbbítja
|
||||
}
|
||||
});
|
||||
|
||||
// A errorHandler middleware automatikusan feldolgozza
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{CORS middleware}
|
||||
\begin{block}{Cross-Origin Resource Sharing}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
const cors = require('cors');
|
||||
|
||||
// Egyszerű CORS - minden origin engedélyezve
|
||||
app.use(cors());
|
||||
|
||||
// Konfigurált CORS
|
||||
const corsOptions = {
|
||||
origin: 'https://frontend.example.com', // Engedélyezett origin
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||
credentials: true, // Cookie-k engedélyezése
|
||||
maxAge: 86400 // Preflight cache 24 óra
|
||||
};
|
||||
|
||||
app.use(cors(corsOptions));
|
||||
|
||||
// Route-specific CORS
|
||||
app.get('/api/public', cors(), (req, res) => {
|
||||
res.json({ message: 'Public data' });
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Security middleware - Helmet}
|
||||
\begin{block}{HTTP header biztonság}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
const helmet = require('helmet');
|
||||
|
||||
// Helmet middleware - biztonságos HTTP headerek
|
||||
app.use(helmet());
|
||||
|
||||
// Helmet beállítja:
|
||||
// - Content-Security-Policy
|
||||
// - X-DNS-Prefetch-Control
|
||||
// - X-Frame-Options (SAMEORIGIN)
|
||||
// - X-Content-Type-Options (nosniff)
|
||||
// - X-XSS-Protection
|
||||
// stb.
|
||||
|
||||
// Egyedi konfiguráció
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'"]
|
||||
}
|
||||
}
|
||||
}));
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Middleware láncolás}
|
||||
\begin{block}{Több middleware egyidejű használata}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
// Middleware láncolás egy route-on
|
||||
app.post('/api/posts',
|
||||
authenticateJWT, // 1. Authentikáció
|
||||
requireRole('user'), // 2. Autorizáció
|
||||
validatePost, // 3. Validáció
|
||||
uploadImages, // 4. Képfeltöltés
|
||||
(req, res) => { // 5. Route handler
|
||||
// Post létrehozás logika
|
||||
res.json({ message: 'Post created' });
|
||||
}
|
||||
);
|
||||
|
||||
// Middleware tömb
|
||||
const postMiddlewares = [
|
||||
authenticateJWT,
|
||||
requireRole('user'),
|
||||
validatePost
|
||||
];
|
||||
|
||||
app.post('/api/posts', postMiddlewares, (req, res) => {
|
||||
// ...
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Middleware Best Practices}
|
||||
\begin{enumerate}
|
||||
\item \textbf{Sorrend számít:}
|
||||
\begin{itemize}
|
||||
\item CORS és security middleware-ek ELŐRE
|
||||
\item Error handler middleware HÁTRA
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.2cm}
|
||||
|
||||
\item \textbf{Mindig hívd meg a next()-et:}
|
||||
\begin{itemize}
|
||||
\item Kivéve ha response-t küldesz vagy hibát dobsz
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.2cm}
|
||||
|
||||
\item \textbf{Error handling:}
|
||||
\begin{itemize}
|
||||
\item Async függvényekben try-catch + next(error)
|
||||
\item 4 paraméteres error handler middleware
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.2cm}
|
||||
|
||||
\item \textbf{Teljesítmény:}
|
||||
\begin{itemize}
|
||||
\item Ne futtass middleware-t szükségtelenül (route-specific)
|
||||
\item Async műveletek csak ha szükséges
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.2cm}
|
||||
|
||||
\item \textbf{Újrafelhasználhatóság:}
|
||||
\begin{itemize}
|
||||
\item Middleware factory pattern (pl. requireRole)
|
||||
\item Konfigurálható middleware-ek
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Middleware execution order}
|
||||
\begin{block}{Tipikus middleware sorrend Express alkalmazásban}
|
||||
\small
|
||||
\begin{enumerate}
|
||||
\item \textbf{helmet()} - Security headers
|
||||
\item \textbf{cors()} - CORS beállítások
|
||||
\item \textbf{morgan/logger} - Request logging
|
||||
\item \textbf{express.json()} - Body parser
|
||||
\item \textbf{express.urlencoded()} - URL-encoded body
|
||||
\item \textbf{cookie-parser()} - Cookie parsing
|
||||
\item \textbf{Rate limiter} - API rate limiting
|
||||
\item \textbf{Custom middlewares} - Saját middleware-ek
|
||||
\item \textbf{Routes} - Route handler-ek
|
||||
\item \textbf{404 handler} - Not found middleware
|
||||
\item \textbf{Error handler} - Központi hibakezelés (4 param)
|
||||
\end{enumerate}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Összefoglalás - Middleware}
|
||||
\begin{itemize}
|
||||
\item \textbf{Middleware} = Függvény a kérés-válasz között
|
||||
\item \textbf{Paraméterek:} (req, res, next) vagy (err, req, res, next)
|
||||
\item \textbf{Típusok:} Application, Router, Error-handling, Built-in, Third-party
|
||||
\item \textbf{next():} Következő middleware-re adja a vezérlést
|
||||
\item \textbf{Használat:}
|
||||
\begin{itemize}
|
||||
\item Logging (morgan, winston)
|
||||
\item Authentication (JWT validáció)
|
||||
\item Authorization (szerepkör ellenőrzés)
|
||||
\item Validation (input ellenőrzés)
|
||||
\item Rate limiting (express-rate-limit)
|
||||
\item Security (helmet, cors)
|
||||
\item Error handling (központi hibakezelés)
|
||||
\end{itemize}
|
||||
\item \textbf{Sorrend fontos:} Security → Parsing → Auth → Routes → Error
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
@@ -0,0 +1,154 @@
|
||||
\section{OAuth 2.0}
|
||||
|
||||
\begin{frame}[shrink=15]{Mi az OAuth 2.0?}
|
||||
\begin{block}{Definíció}
|
||||
Autorizációs protokoll - korlátozott hozzáférés jelszó megosztása nélkül.
|
||||
\end{block}
|
||||
\begin{itemize}
|
||||
\item RFC 6749 (2012)
|
||||
\item Google, Facebook, GitHub használja
|
||||
\item Példa: App hozzáfér Google Drive-hoz jelszó nélkül
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{OAuth 2.0 szerepkörök}
|
||||
\begin{enumerate}
|
||||
\item \textbf{Resource Owner}: Felhasználó (birtokolja erőforrást)
|
||||
\item \textbf{Client}: Alkalmazás (hozzáférést kér)
|
||||
\item \textbf{Authorization Server}: Token kiállító
|
||||
\item \textbf{Resource Server}: Védett erőforrás tároló
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{OAuth 2.0 Flow}
|
||||
\begin{center}
|
||||
\begin{tikzpicture}[node distance=1.8cm, auto]
|
||||
\node (ro) [rectangle, draw, text width=1.8cm, text centered] {Resource Owner};
|
||||
\node (client) [rectangle, draw, below of=ro, text width=1.8cm, text centered] {Client};
|
||||
\node (auth) [rectangle, draw, right of=client, xshift=2.5cm, text width=2cm, text centered] {Auth Server};
|
||||
\node (res) [rectangle, draw, below of=auth, text width=2cm, text centered] {Resource Server};
|
||||
|
||||
\draw[->, thick] (client) -- node[left] {1. Kérés} (ro);
|
||||
\draw[->, thick] (ro) -- node[above] {2. OK} (auth);
|
||||
\draw[->, thick] (auth) -- node[right] {3. Token} (client);
|
||||
\draw[->, thick] (client) -- node[above] {4. API} (res);
|
||||
\draw[->, thick] (res) -- node[below] {5. Data} (client);
|
||||
\end{tikzpicture}
|
||||
\end{center}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{Grant Types}
|
||||
\begin{enumerate}
|
||||
\item \textbf{Authorization Code}: Szerver app, legbiztonságosabb
|
||||
\item \textbf{Implicit}: \textcolor{red}{Elavult!}
|
||||
\item \textbf{Resource Owner Password}: Közvetlen jelszó, megbízható app
|
||||
\item \textbf{Client Credentials}: Machine-to-machine
|
||||
\item \textbf{PKCE}: Modern mobil/SPA
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=20]{Authorization Code Flow lépések}
|
||||
\begin{enumerate}
|
||||
\item Kliens átirányít: \texttt{/authorize?client\_id=\&redirect\_uri=\&scope=}
|
||||
\item Felhasználó bejelentkezik és hozzájárul
|
||||
\item Auth Server visszairányít: \texttt{redirect\_uri?code=AUTH\_CODE}
|
||||
\item Kliens kicseréli code-ot: POST \texttt{/token}
|
||||
\item Auth Server ad access token-t (+ refresh)
|
||||
\item Kliens használja tokent: \texttt{Authorization: Bearer <token>}
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{Authorization Code - Auth kérés}
|
||||
\begin{verbatim}
|
||||
GET /oauth/authorize?
|
||||
response_type=code&
|
||||
client_id=YOUR_CLIENT_ID&
|
||||
redirect_uri=https://app.com/callback&
|
||||
scope=read write&
|
||||
state=xyz123
|
||||
\end{verbatim}
|
||||
\begin{verbatim}
|
||||
// Callback
|
||||
GET https://app.com/callback?
|
||||
code=AUTH_CODE&
|
||||
state=xyz123
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{Authorization Code - Token csere}
|
||||
\begin{verbatim}
|
||||
POST /oauth/token
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=authorization_code&
|
||||
code=AUTH_CODE&
|
||||
redirect_uri=https://app.com/callback&
|
||||
client_id=YOUR_ID&
|
||||
client_secret=YOUR_SECRET
|
||||
\end{verbatim}
|
||||
\begin{verbatim}
|
||||
// Válasz
|
||||
{
|
||||
"access_token": "eyJ...",
|
||||
"expires_in": 3600,
|
||||
"refresh_token": "tGzv..."
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{PKCE (Proof Key for Code Exchange)}
|
||||
\begin{block}{Miért?}
|
||||
Authorization Code Flow biztonságosabbá tétele mobil/SPA app-okhoz.
|
||||
\end{block}
|
||||
\begin{enumerate}
|
||||
\item Kliens generál \texttt{code\_verifier} (random string)
|
||||
\item Hash: \texttt{code\_challenge = SHA256(code\_verifier)}
|
||||
\item Auth kéréshez csatol: \texttt{code\_challenge}
|
||||
\item Token kérésnél küldi: \texttt{code\_verifier}
|
||||
\item Server validálja: \texttt{SHA256(verifier) == challenge}
|
||||
\end{enumerate}
|
||||
\begin{alertblock}{Védelem}
|
||||
Megakadályozza authorization code ellopását.
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{Scope és Permission}
|
||||
\begin{block}{Scope}
|
||||
Jogosultság amit app kér. Példa: \texttt{read:user write:repo}
|
||||
\end{block}
|
||||
\begin{itemize}
|
||||
\item Felhasználó látja mit kér az app
|
||||
\item Csak kért scope-okat kapja meg
|
||||
\item Token tartalmazza scope-okat
|
||||
\end{itemize}
|
||||
\begin{exampleblock}{GitHub példa}
|
||||
\texttt{repo, user, gist, notifications}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{Access Token használata}
|
||||
\begin{verbatim}
|
||||
// API kérés Bearer token-nel
|
||||
GET /api/user/profile
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
|
||||
\end{verbatim}
|
||||
\begin{verbatim}
|
||||
// Express validálás
|
||||
app.get('/api/user', (req, res) => {
|
||||
const token = req.headers.authorization?.split(' ')[1];
|
||||
// Token validálás...
|
||||
res.json({ user: userData });
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{OAuth 2.0 Security}
|
||||
\begin{enumerate}
|
||||
\item \textbf{HTTPS kötelező}: Minden kommunikáció
|
||||
\item \textbf{State parameter}: CSRF védelem
|
||||
\item \textbf{Redirect URI validation}: Előre regisztrált URI
|
||||
\item \textbf{Short token lifetime}: Access token max 1 óra
|
||||
\item \textbf{PKCE használata}: Mobil/SPA app-okhoz
|
||||
\item \textbf{Scope limitation}: Csak szükséges jogok
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
@@ -0,0 +1,443 @@
|
||||
\section{OAuth 2.0}
|
||||
|
||||
\begin{frame}{Mi az OAuth 2.0?}
|
||||
\begin{block}{Definíció}
|
||||
OAuth 2.0 egy nyílt szabványú \textbf{autorizációs protokoll}, amely korlátozott hozzáférést ad jelszó megosztása nélkül.
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item RFC 6749 (2012)
|
||||
\item Autorizációra fókuszál
|
||||
\item Biztonságos delegált hozzáférés
|
||||
\item Google, Facebook, GitHub
|
||||
\end{itemize}
|
||||
|
||||
\begin{exampleblock}{Használat}
|
||||
Alkalmazás hozzáfér Google Drive-hoz anélkül, hogy ismebné a jelszót.
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{OAuth 2.0 szerepkörök}
|
||||
\begin{enumerate}
|
||||
\item \textbf{Resource Owner (Erőforrás tulajdonos)}
|
||||
\begin{itemize}
|
||||
\item A felhasználó, aki birtokolja az erőforrást
|
||||
\item Engedélyezheti a hozzáférést
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\item \textbf{Client (Kliens)}
|
||||
\begin{itemize}
|
||||
\item Az alkalmazás, amely hozzáférést kér az erőforráshoz
|
||||
\item Például: mobil app, webalkalmazás
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\item \textbf{Authorization Server (Autorizációs szerver)}
|
||||
\begin{itemize}
|
||||
\item Kiállítja az access token-t sikeres authentikáció után
|
||||
\item Kezeli a felhasználói hozzájárulást
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\item \textbf{Resource Server (Erőforrás szerver)}
|
||||
\begin{itemize}
|
||||
\item Tárolja a védett erőforrásokat
|
||||
\item Elfogadja és validálja az access token-eket
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{OAuth 2.0 Flow - Áttekintés}
|
||||
\begin{center}
|
||||
\begin{tikzpicture}[node distance=2cm, auto]
|
||||
\node (ro) [rectangle, draw, text width=2cm, text centered] {Resource Owner};
|
||||
\node (client) [rectangle, draw, below of=ro, text width=2cm, text centered] {Client};
|
||||
\node (authserver) [rectangle, draw, right of=client, xshift=3cm, text width=2.5cm, text centered] {Authorization Server};
|
||||
\node (resserver) [rectangle, draw, below of=authserver, text width=2.5cm, text centered] {Resource Server};
|
||||
|
||||
\draw[->, thick] (client) -- node[left] {1. Kérés} (ro);
|
||||
\draw[->, thick] (ro) -- node[above] {2. Hozzájárulás} (authserver);
|
||||
\draw[->, thick] (authserver) -- node[right] {3. Token} (client);
|
||||
\draw[->, thick] (client) -- node[above] {4. API kérés} (resserver);
|
||||
\draw[->, thick] (resserver) -- node[below] {5. Védett erőforrás} (client);
|
||||
\end{tikzpicture}
|
||||
\end{center}
|
||||
|
||||
\begin{itemize}
|
||||
\item Az Authorization Server és Resource Server lehet ugyanaz a rendszer
|
||||
\item A kommunikáció HTTPS-en keresztül történik
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=10]{OAuth 2.0 Grant Types}
|
||||
\begin{block}{Grant Type}
|
||||
Módszer, ahogyan kliens access token-t szerez.
|
||||
\end{block}
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{Authorization Code}
|
||||
\begin{itemize}
|
||||
\item Legbiztonságosabb, szerveroldali app-hoz
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Implicit}
|
||||
\begin{itemize}
|
||||
\item \textcolor{red}{Elavult!}
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Resource Owner Password}
|
||||
\begin{itemize}
|
||||
\item Közvetlen jelszó, megbízható app-okhoz
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Client Credentials}
|
||||
\begin{itemize}
|
||||
\item Machine-to-machine
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{PKCE}
|
||||
\begin{itemize}
|
||||
\item Modern mobil és SPA-hoz
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=10]{Authorization Code Flow}
|
||||
\begin{block}{Leggyakrabban használt flow}
|
||||
Biztonságos szerveroldali alkalmazásokhoz, ahol a client secret biztonságosan tárolható.
|
||||
\end{block}
|
||||
|
||||
\begin{enumerate}
|
||||
\small
|
||||
\item \textbf{Kliens átirányítja a felhasználót} az Authorization Server-re
|
||||
\begin{itemize}
|
||||
\item URL: \texttt{/authorize?client\_id=\&redirect\_uri=\&scope=\&state=}
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Felhasználó bejelentkezik és hozzájárul}
|
||||
|
||||
\item \textbf{Authorization Server visszairányít} authorization code-dal
|
||||
\begin{itemize}
|
||||
\item URL: \texttt{redirect\_uri?code=AUTH\_CODE\&state=}
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Kliens kicseréli a code-ot} access token-re
|
||||
\begin{itemize}
|
||||
\item POST: \texttt{/token} (client\_id, client\_secret, code, redirect\_uri)
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Authorization Server visszaad} access token-t (és refresh token-t)
|
||||
|
||||
\item \textbf{Kliens használja az access token-t} API kérésekhez
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=10]{Authorization Code Flow - Példa}
|
||||
\begin{block}{1. Felhasználó átirányítása az authorization endpoint-ra}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
GET /oauth/authorize?
|
||||
response_type=code&
|
||||
client_id=YOUR_CLIENT_ID&
|
||||
redirect_uri=https://yourapp.com/callback&
|
||||
scope=read write&
|
||||
state=xyz123
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
|
||||
\begin{block}{2. Callback authorization code-dal}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
GET https://yourapp.com/callback?
|
||||
code=AUTH_CODE_HERE&
|
||||
state=xyz123
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=10]{Authorization Code Flow - Példa (folyt.)}
|
||||
\begin{block}{3. Token kérés}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
POST /oauth/token
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=authorization_code&
|
||||
code=AUTH_CODE_HERE&
|
||||
redirect_uri=https://yourapp.com/callback&
|
||||
client_id=YOUR_CLIENT_ID&
|
||||
client_secret=YOUR_CLIENT_SECRET
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
|
||||
\begin{block}{4. Token válasz}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
{
|
||||
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600,
|
||||
"refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
|
||||
"scope": "read write"
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{PKCE (Proof Key for Code Exchange)}
|
||||
\begin{block}{Miért szükséges?}
|
||||
A natív és SPA (Single Page Application) alkalmazások nem tudják biztonságosan tárolni a client secret-et. A PKCE authorization code interception elleni védelmet nyújt.
|
||||
\end{block}
|
||||
|
||||
\vspace{0.5cm}
|
||||
|
||||
\begin{enumerate}
|
||||
\item Kliens generál egy random \textbf{code\_verifier} értéket
|
||||
\item Létrehoz egy \textbf{code\_challenge}-t a verifier-ből
|
||||
\begin{itemize}
|
||||
\item \texttt{code\_challenge = BASE64URL(SHA256(code\_verifier))}
|
||||
\end{itemize}
|
||||
\item Authorization kéréshez csatolja a \texttt{code\_challenge}-t
|
||||
\item Token kéréshez csatolja a \texttt{code\_verifier}-t
|
||||
\item Szerver ellenőrzi, hogy a verifier megfelel-e a challenge-nek
|
||||
\end{enumerate}
|
||||
|
||||
\vspace{0.5cm}
|
||||
|
||||
\begin{alertblock}{Fontos!}
|
||||
A PKCE ma már \textbf{minden} OAuth 2.0 flow-hoz ajánlott!
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Client Credentials Grant}
|
||||
\begin{block}{Machine-to-Machine hitelesítés}
|
||||
Amikor nincs felhasználói interakció, csak két rendszer kommunikál egymással.
|
||||
\end{block}
|
||||
|
||||
\vspace{0.5cm}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Használati esetek:}
|
||||
\begin{itemize}
|
||||
\item Backend szolgáltatások közötti kommunikáció
|
||||
\item Cron job-ok, scheduled task-ok
|
||||
\item CLI eszközök
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\item \textbf{Folyamat:}
|
||||
\begin{enumerate}
|
||||
\item Kliens küld egy POST kérést a token endpoint-ra
|
||||
\item Csatolja a client\_id-t és client\_secret-et
|
||||
\item Szerver visszaadja az access token-t
|
||||
\item Kliens használja a token-t API kérésekhez
|
||||
\end{enumerate}
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\begin{alertblock}{Figyelem!}
|
||||
Nincs refresh token, mert nincs felhasználói kontextus!
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Client Credentials Grant - Példa}
|
||||
\begin{block}{Token kérés}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
POST /oauth/token
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Authorization: Basic BASE64(client_id:client_secret)
|
||||
|
||||
grant_type=client_credentials&
|
||||
scope=api.read
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\begin{block}{Token válasz}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
{
|
||||
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600,
|
||||
"scope": "api.read"
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Access Token és Refresh Token}
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{block}{Access Token}
|
||||
\begin{itemize}
|
||||
\item Rövid élettartam (pl. 1 óra)
|
||||
\item API kérésekhez használt
|
||||
\item Bearer token formátum
|
||||
\item Gyakran JWT
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\textbf{Használat:}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
Authorization: Bearer
|
||||
ACCESS_TOKEN
|
||||
\end{verbatim}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{block}{Refresh Token}
|
||||
\begin{itemize}
|
||||
\item Hosszú élettartam (napok/hetek)
|
||||
\item Új access token kéréséhez
|
||||
\item Biztonságosan tárolva
|
||||
\item Opaque token
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\textbf{Token megújítás:}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
POST /oauth/token
|
||||
grant_type=refresh_token&
|
||||
refresh_token=REFRESH_TOKEN
|
||||
\end{verbatim}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
|
||||
\vspace{0.5cm}
|
||||
|
||||
\begin{alertblock}{Best Practice}
|
||||
Az access token lejárta esetén a refresh token-nel új tokent kérünk, nem kell újra bejelentkeztetni a felhasználót!
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Scopes (Hatókörök)}
|
||||
\begin{block}{Mi az a scope?}
|
||||
A \textbf{scope} meghatározza, hogy a kliens milyen erőforrásokhoz és milyen műveletekhez férhet hozzá.
|
||||
\end{block}
|
||||
|
||||
\vspace{0.5cm}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Granulált jogosultságok:}
|
||||
\begin{itemize}
|
||||
\item \texttt{read} - olvasási jogosultság
|
||||
\item \texttt{write} - írási jogosultság
|
||||
\item \texttt{delete} - törlési jogosultság
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\item \textbf{Erőforrás specifikus:}
|
||||
\begin{itemize}
|
||||
\item \texttt{user:email} - email cím olvasása
|
||||
\item \texttt{repo:write} - repository írása
|
||||
\item \texttt{profile:read} - profil olvasása
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\item \textbf{Best Practices:}
|
||||
\begin{itemize}
|
||||
\item Least privilege principle: csak a szükséges scope-ok
|
||||
\item Egyértelmű elnevezés
|
||||
\item Dokumentáció minden scope-hoz
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{OpenID Connect (OIDC)}
|
||||
\begin{block}{Authentikációs réteg OAuth 2.0 felett}
|
||||
Az \textbf{OpenID Connect} kiegészíti az OAuth 2.0-t authentikációs funkcionalitással.
|
||||
\end{block}
|
||||
|
||||
\vspace{0.5cm}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{OAuth 2.0} = Autorizáció
|
||||
\item \textbf{OpenID Connect} = Autorizáció + Authentikáció
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\item \textbf{ID Token:}
|
||||
\begin{itemize}
|
||||
\item JWT formátumú token
|
||||
\item Tartalmazza a felhasználó azonosító adatait
|
||||
\item Claims: \texttt{sub}, \texttt{name}, \texttt{email}, \texttt{picture}, stb.
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\item \textbf{UserInfo Endpoint:}
|
||||
\begin{itemize}
|
||||
\item További felhasználói információk lekérdezése
|
||||
\item Access token-nel hívható
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\item \textbf{Használat:} "Sign in with Google", "Login with Facebook"
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{OAuth 2.0 Biztonsági best practices}
|
||||
\begin{alertblock}{Kritikus biztonsági szempontok}
|
||||
\begin{itemize}
|
||||
\item \textbf{HTTPS kötelező} minden kommunikációhoz!
|
||||
\item \textbf{State paraméter} használata CSRF védelem miatt
|
||||
\item \textbf{Client secret} biztonságos tárolása (szerver oldalon)
|
||||
\item \textbf{Redirect URI validáció} - csak engedélyezett URI-k
|
||||
\item \textbf{Token lejárati idő} beállítása
|
||||
\item \textbf{Scope minimalizálása} - least privilege
|
||||
\end{itemize}
|
||||
\end{alertblock}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\begin{block}{További ajánlások}
|
||||
\begin{itemize}
|
||||
\item PKCE használata minden flow-ban
|
||||
\item Token revocation támogatása
|
||||
\item Rate limiting az endpoint-okon
|
||||
\item Token tárolás biztonságos helyen (HttpOnly cookie)
|
||||
\item Regular security audit
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Összefoglalás}
|
||||
\begin{itemize}
|
||||
\item \textbf{OAuth 2.0} = Autorizációs protokoll (nem authentikációs!)
|
||||
\item \textbf{Szerepkörök:} Resource Owner, Client, Authorization Server, Resource Server
|
||||
\item \textbf{Grant Types:} Authorization Code (PKCE), Client Credentials
|
||||
\item \textbf{Tokenek:} Access Token (rövid), Refresh Token (hosszú)
|
||||
\item \textbf{Scopes:} Granulált jogosultság kezelés
|
||||
\item \textbf{OpenID Connect:} OAuth 2.0 + Authentikáció
|
||||
\item \textbf{Biztonság:} HTTPS, state, PKCE, token biztonság
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.5cm}
|
||||
|
||||
\begin{exampleblock}{Amikor OAuth 2.0-t használunk}
|
||||
\begin{itemize}
|
||||
\item Social login implementációhoz
|
||||
\item Third-party API hozzáféréshez
|
||||
\item Microservice authentikációhoz
|
||||
\item Mobile és SPA alkalmazásokhoz
|
||||
\end{itemize}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
@@ -0,0 +1,233 @@
|
||||
\section{Service Layer}
|
||||
|
||||
\begin{frame}[shrink=15]{Mi az a Service Layer?}
|
||||
\begin{block}{Definíció}
|
||||
Tervezési minta - üzleti logika elválasztása controller-ektől és data layer-től.
|
||||
\end{block}
|
||||
\begin{itemize}
|
||||
\item Separation of Concerns
|
||||
\item Reusability (újrafelhasználható logika)
|
||||
\item Testability (könnyebb tesztelés)
|
||||
\item Maintainability
|
||||
\end{itemize}
|
||||
\begin{exampleblock}{Architektúra}
|
||||
Controller → Service → Repository → Database
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{Háromrétegű architektúra}
|
||||
\begin{columns}
|
||||
\begin{column}{0.32\textwidth}
|
||||
\begin{block}{Presentation}
|
||||
\begin{small}
|
||||
Controllers, Routes\\
|
||||
HTTP req/res\\
|
||||
Validáció
|
||||
\end{small}
|
||||
\end{block}
|
||||
\end{column}
|
||||
\begin{column}{0.32\textwidth}
|
||||
\begin{block}{Business Logic}
|
||||
\begin{small}
|
||||
\textbf{Services}\\
|
||||
Üzleti szabályok\\
|
||||
Orchestration
|
||||
\end{small}
|
||||
\end{block}
|
||||
\end{column}
|
||||
\begin{column}{0.32\textwidth}
|
||||
\begin{block}{Data Access}
|
||||
\begin{small}
|
||||
Repository\\
|
||||
ORM\\
|
||||
Database
|
||||
\end{small}
|
||||
\end{block}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\begin{alertblock}{Fontos}
|
||||
Controller \textbf{NEM} tartalmaz üzleti logikát, csak delegál!
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{Service Layer előnyei}
|
||||
\begin{enumerate}
|
||||
\item \textbf{Separation}: Tiszta felelősségi körök
|
||||
\item \textbf{Reusability}: Több controller használhatja
|
||||
\item \textbf{Testability}: Könnyen unit tesztelhető
|
||||
\item \textbf{Maintainability}: Változások izoláltak
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{AuthService - Interface}
|
||||
\begin{verbatim}
|
||||
class AuthService {
|
||||
async register(userData) { ... }
|
||||
async login(email, password) { ... }
|
||||
async logout(userId) { ... }
|
||||
async refreshToken(refreshToken) { ... }
|
||||
async verifyToken(token) { ... }
|
||||
async resetPassword(email) { ... }
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{AuthService - Register}
|
||||
\begin{verbatim}
|
||||
class AuthService {
|
||||
async register(userData) {
|
||||
const exists = await this.userRepo.findByEmail(userData.email);
|
||||
if (exists) throw new Error('User exists');
|
||||
|
||||
const hashed = await bcrypt.hash(userData.password, 10);
|
||||
const user = await this.userRepo.create({
|
||||
...userData,
|
||||
password: hashed
|
||||
});
|
||||
|
||||
return { id: user.id, email: user.email };
|
||||
}
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{AuthService - Login}
|
||||
\begin{verbatim}
|
||||
async login(email, password) {
|
||||
const user = await this.userRepo.findByEmail(email);
|
||||
if (!user) throw new Error('Invalid credentials');
|
||||
|
||||
const valid = await bcrypt.compare(password, user.password);
|
||||
if (!valid) throw new Error('Invalid credentials');
|
||||
|
||||
const token = jwt.sign(
|
||||
{ userId: user.id, role: user.role },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '1h' }
|
||||
);
|
||||
|
||||
return { token, user: { id: user.id, email: user.email } };
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{Controller használja Service-t}
|
||||
\begin{verbatim}
|
||||
class AuthController {
|
||||
constructor(authService) {
|
||||
this.authService = authService;
|
||||
}
|
||||
|
||||
async register(req, res) {
|
||||
try {
|
||||
const user = await this.authService.register(req.body);
|
||||
res.status(201).json(user);
|
||||
} catch (err) {
|
||||
res.status(400).json({ error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
async login(req, res) {
|
||||
try {
|
||||
const result = await this.authService.login(req.body.email, req.body.password);
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
res.status(401).json({ error: err.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{UserService példa}
|
||||
\begin{verbatim}
|
||||
class UserService {
|
||||
constructor(userRepo) {
|
||||
this.userRepo = userRepo;
|
||||
}
|
||||
|
||||
async getById(id) {
|
||||
const user = await this.userRepo.findById(id);
|
||||
if (!user) throw new Error('User not found');
|
||||
return user;
|
||||
}
|
||||
|
||||
async update(id, data) {
|
||||
const user = await this.getById(id);
|
||||
return await this.userRepo.update(id, data);
|
||||
}
|
||||
|
||||
async delete(id) {
|
||||
await this.getById(id);
|
||||
return await this.userRepo.delete(id);
|
||||
}
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{Dependency Injection}
|
||||
\begin{verbatim}
|
||||
// Repository
|
||||
class UserRepository {
|
||||
async findById(id) { /* DB query */ }
|
||||
async create(data) { /* DB insert */ }
|
||||
}
|
||||
|
||||
// Service
|
||||
class UserService {
|
||||
constructor(userRepository) {
|
||||
this.userRepo = userRepository;
|
||||
}
|
||||
// ...
|
||||
}
|
||||
|
||||
// DI container
|
||||
const userRepo = new UserRepository();
|
||||
const userService = new UserService(userRepo);
|
||||
const userController = new UserController(userService);
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{Service Layer Best Practices}
|
||||
\begin{enumerate}
|
||||
\item Service-ek ne dependáljanak controller-ektől
|
||||
\item Egy service = egy domain (User, Auth, Order)
|
||||
\item Dependency Injection használata
|
||||
\item Service-ek ne ismerjék HTTP-t (req, res)
|
||||
\item Hibakezelés service-ben (throw Error)
|
||||
\item Transaction logika service-ben
|
||||
\item Async/await következetes használata
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=20]{Transaction példa Service-ben}
|
||||
\begin{verbatim}
|
||||
class OrderService {
|
||||
async createOrder(userId, items) {
|
||||
const transaction = await db.transaction();
|
||||
|
||||
try {
|
||||
const order = await this.orderRepo.create({ userId }, transaction);
|
||||
|
||||
for (const item of items) {
|
||||
await this.orderItemRepo.create({ orderId: order.id, ...item }, transaction);
|
||||
}
|
||||
|
||||
await transaction.commit();
|
||||
return order;
|
||||
} catch (err) {
|
||||
await transaction.rollback();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{Testing Service Layer}
|
||||
\begin{itemize}
|
||||
\item Service-ek unit tesztelése mock repository-val
|
||||
\item Ne kelljen DB a unit teszthez
|
||||
\item Integration teszt valódi DB-vel
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
@@ -0,0 +1,671 @@
|
||||
\section{Service Layer}
|
||||
|
||||
\begin{frame}[shrink=5]{Mi az a Service Layer?}
|
||||
\begin{block}{Definíció}
|
||||
A \textbf{Service Layer} (szolgáltatási réteg) egy tervezési minta, amely elválasztja az üzleti logikát a controller-ektől és a data access layer-től.
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Separation of Concerns:} Felelősségek szétválasztása
|
||||
\item \textbf{Reusability:} Újrafelhasználható üzleti logika
|
||||
\item \textbf{Testability:} Könnyebb tesztelhetőség
|
||||
\item \textbf{Maintainability:} Karbantarthatóság
|
||||
\end{itemize}
|
||||
|
||||
\begin{exampleblock}{MVC architektúrában}
|
||||
Controller → \textbf{Service Layer} → Repository/Model → Database
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=5]{Háromrétegű architektúra}
|
||||
\begin{columns}
|
||||
\begin{column}{0.3\textwidth}
|
||||
\begin{block}{Presentation Layer}
|
||||
\begin{itemize}
|
||||
\item Controllers
|
||||
\item Routes
|
||||
\item HTTP kérés/válasz
|
||||
\item Validáció
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.35\textwidth}
|
||||
\begin{block}{Business Logic Layer}
|
||||
\begin{itemize}
|
||||
\item \textbf{Services}
|
||||
\item Üzleti szabályok
|
||||
\item Adatmanipuláció
|
||||
\item Orchestration
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.3\textwidth}
|
||||
\begin{block}{Data Access Layer}
|
||||
\begin{itemize}
|
||||
\item Repository
|
||||
\item Models
|
||||
\item ORM
|
||||
\item Database
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
|
||||
\begin{alertblock}{Fontos!}
|
||||
A controller \textbf{NEM} tartalmaz üzleti logikát, csak delegál a service-eknek!
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=10]{Service Layer előnyei}
|
||||
\begin{enumerate}
|
||||
\item \textbf{Separation of Concerns}
|
||||
\begin{itemize}
|
||||
\item Tiszta felelősségi körök
|
||||
\item Controller: HTTP kezelés
|
||||
\item Service: Üzleti logika
|
||||
\item Repository: Adatelérés
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Reusability}
|
||||
\begin{itemize}
|
||||
\item Több controller használhatja ugyanazt a service-t
|
||||
\item Különböző kontextusokban (API, CLI, Background job)
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Testability}
|
||||
\begin{itemize}
|
||||
\item Service-ek könnyebben unit tesztelhetők
|
||||
\item Mock-olható függőségek
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Maintainability}
|
||||
\begin{itemize}
|
||||
\item Változások izoláltak
|
||||
\item Könnyebb hibakeresés
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=5]{AuthService - Authentikációs szolgáltatás}
|
||||
\begin{block}{AuthService felelősségei}
|
||||
\begin{itemize}
|
||||
\item Felhasználó regisztráció
|
||||
\item Bejelentkezés (login)
|
||||
\item Token generálás és validálás
|
||||
\item Jelszó hash-elés
|
||||
\item Kijelentkezés (logout)
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
|
||||
\begin{exampleblock}{AuthService példa}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
class AuthService {
|
||||
async register(userData) { ... }
|
||||
async login(email, password) { ... }
|
||||
async logout(userId) { ... }
|
||||
async refreshToken(refreshToken) { ... }
|
||||
async verifyToken(token) { ... }
|
||||
async resetPassword(email) { ... }
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=10]{AuthService implementáció - Register}
|
||||
\begin{block}{Regisztráció üzleti logikája}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
class AuthService {
|
||||
async register(userData) {
|
||||
const existing = await this.userRepository
|
||||
.findByEmail(userData.email);
|
||||
if (existing) throw new Error('User exists');
|
||||
|
||||
const hashed = await bcrypt.hash(userData.password, 10);
|
||||
const user = await this.userRepository.create({
|
||||
...userData,
|
||||
password: hashed
|
||||
});
|
||||
|
||||
return { id: user.id, email: user.email };
|
||||
}
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{AuthService implementáció - Login}
|
||||
\begin{block}{Bejelentkezés üzleti logikája}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
async login(email, password) {
|
||||
const user = await this.userRepository.findByEmail(email);
|
||||
if (!user) throw new Error('Invalid credentials');
|
||||
|
||||
const valid = await bcrypt.compare(password, user.password);
|
||||
if (!valid) throw new Error('Invalid credentials');
|
||||
|
||||
const accessToken = jwt.sign(
|
||||
{ userId: user.id, email: user.email },
|
||||
process.env.JWT_SECRET, { expiresIn: '15m' }
|
||||
);
|
||||
|
||||
const refreshToken = jwt.sign(
|
||||
{ userId: user.id },
|
||||
process.env.REFRESH_SECRET, { expiresIn: '7d' }
|
||||
);
|
||||
|
||||
return { accessToken, refreshToken };
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Controller használja az AuthService-t}
|
||||
\begin{block}{Thin Controller - Fat Service}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
app.post('/api/auth/register', async (req, res) => {
|
||||
try {
|
||||
const user = await authService.register(req.body);
|
||||
res.status(201).json({ user });
|
||||
} catch (error) {
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/auth/login', async (req, res) => {
|
||||
try {
|
||||
const result = await authService.login(
|
||||
req.body.email, req.body.password
|
||||
);
|
||||
res.cookie('refreshToken', result.refreshToken,
|
||||
{ httpOnly: true, secure: true });
|
||||
res.json({ accessToken: result.accessToken });
|
||||
} catch (error) {
|
||||
res.status(401).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{UserService - Felhasználó kezelés}
|
||||
\begin{block}{UserService felelősségei}
|
||||
\begin{itemize}
|
||||
\item Felhasználói profil lekérdezés
|
||||
\item Profil módosítás
|
||||
\item Jelszó változtatás
|
||||
\item Felhasználó törlés
|
||||
\item Felhasználó lista (admin)
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\begin{exampleblock}{UserService példa}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
class UserService {
|
||||
constructor(userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
async getUserProfile(userId) { ... }
|
||||
async updateProfile(userId, data) { ... }
|
||||
async changePassword(userId, oldPassword, newPassword) { ... }
|
||||
async deleteUser(userId) { ... }
|
||||
async getAllUsers(filters) { ... }
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=5]{UserService implementáció}
|
||||
\begin{block}{Jelszó változtatás üzleti logikával}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
class UserService {
|
||||
async changePassword(userId, oldPass, newPass) {
|
||||
const user = await this.userRepository.findById(userId);
|
||||
if (!user) throw new Error('User not found');
|
||||
|
||||
const valid = await bcrypt.compare(oldPass, user.password);
|
||||
if (!valid) throw new Error('Incorrect password');
|
||||
|
||||
const hashed = await bcrypt.hash(newPass, 10);
|
||||
await this.userRepository.update(userId,
|
||||
{ password: hashed });
|
||||
await this.tokenService.revokeAllTokens(userId);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{TokenService - Token kezelés}
|
||||
\begin{block}{TokenService felelősségei}
|
||||
\begin{itemize}
|
||||
\item Access token generálás
|
||||
\item Refresh token generálás
|
||||
\item Token validálás
|
||||
\item Token megújítás (refresh)
|
||||
\item Token visszavonás (revoke)
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\begin{exampleblock}{TokenService példa}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
class TokenService {
|
||||
constructor(tokenRepository) {
|
||||
this.tokenRepository = tokenRepository;
|
||||
}
|
||||
|
||||
generateAccessToken(payload) { ... }
|
||||
generateRefreshToken(userId) { ... }
|
||||
async verifyAccessToken(token) { ... }
|
||||
async refreshAccessToken(refreshToken) { ... }
|
||||
async revokeToken(token) { ... }
|
||||
async revokeAllTokens(userId) { ... }
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=5]{TokenService implementáció}
|
||||
\begin{block}{Token refresh implementáció}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
class TokenService {
|
||||
async refreshAccessToken(refreshToken) {
|
||||
const decoded = jwt.verify(
|
||||
refreshToken, process.env.REFRESH_SECRET);
|
||||
|
||||
const stored = await this.tokenRepository
|
||||
.findByToken(refreshToken);
|
||||
if (!stored || stored.revoked)
|
||||
throw new Error('Token revoked');
|
||||
|
||||
const user = await this.userRepository
|
||||
.findById(decoded.userId);
|
||||
return {
|
||||
accessToken: this.generateAccessToken(user)
|
||||
};
|
||||
}
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{AuthorizationService - Autorizáció}
|
||||
\begin{block}{AuthorizationService felelősségei}
|
||||
\begin{itemize}
|
||||
\item Jogosultság ellenőrzés
|
||||
\item Role-based access control (RBAC)
|
||||
\item Permission-based access control (PBAC)
|
||||
\item Resource ownership ellenőrzés
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\begin{exampleblock}{AuthorizationService példa}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
class AuthorizationService {
|
||||
async hasRole(userId, role) { ... }
|
||||
async hasPermission(userId, permission) { ... }
|
||||
async canAccessResource(userId, resourceId, action) { ... }
|
||||
async isOwner(userId, resourceId) { ... }
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{AuthorizationService implementáció}
|
||||
\begin{block}{Jogosultság ellenőrzés}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
class AuthorizationService {
|
||||
constructor(userRepository, permissionRepository) {
|
||||
this.userRepository = userRepository;
|
||||
this.permissionRepository = permissionRepository;
|
||||
}
|
||||
|
||||
async canAccessResource(userId, resourceId, action) {
|
||||
const user = await this.userRepository.findById(userId);
|
||||
|
||||
// 1. Admin mindent csinálhat
|
||||
if (user.role === 'admin') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. Ownership ellenőrzés
|
||||
const resource = await this.resourceRepository.findById(resourceId);
|
||||
if (resource.ownerId === userId && action === 'read') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. Permission alapú ellenőrzés
|
||||
const permissions = await this.permissionRepository.getByUserId(userId);
|
||||
return permissions.some(p => p.action === action && p.resource === resourceId);
|
||||
}
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Dependency Injection}
|
||||
\begin{block}{Service-ek kapcsolata}
|
||||
A service-ek más service-eket használnak. Dependency Injection segít a függőségek kezelésében.
|
||||
\end{block}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Rossz példa:}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
class AuthService {
|
||||
constructor() {
|
||||
this.userRepo =
|
||||
new UserRepository();
|
||||
}
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Jó példa (DI):}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
class AuthService {
|
||||
constructor(userRepo) {
|
||||
this.userRepo = userRepo;
|
||||
}
|
||||
}
|
||||
|
||||
const authService =
|
||||
new AuthService(userRepo);
|
||||
\end{verbatim}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Service Container / DI Container}
|
||||
\begin{block}{Automatikus Dependency Injection}
|
||||
DI container-ek automatikusan kezelik a szolgáltatások létrehozását.
|
||||
\end{block}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\begin{exampleblock}{Awilix library használata}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
const { createContainer, asClass } = require('awilix');
|
||||
|
||||
const container = createContainer();
|
||||
container.register({
|
||||
userRepository: asClass(UserRepository).singleton(),
|
||||
tokenRepository: asClass(TokenRepository).singleton(),
|
||||
authService: asClass(AuthService).singleton(),
|
||||
userService: asClass(UserService).singleton(),
|
||||
tokenService: asClass(TokenService).singleton()
|
||||
});
|
||||
|
||||
const authService = container.resolve('authService');
|
||||
\end{verbatim}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Error Handling a Service Layer-ben}
|
||||
\begin{block}{Custom Error osztályok}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
class AuthenticationError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.statusCode = 401;
|
||||
}
|
||||
}
|
||||
|
||||
class AuthorizationError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.statusCode = 403;
|
||||
}
|
||||
}
|
||||
|
||||
class NotFoundError extends Error {
|
||||
constructor(resource) {
|
||||
super(`${resource} not found`);
|
||||
this.statusCode = 404;
|
||||
}
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Error Handling - Service használat}
|
||||
\begin{block}{Service dobja a custom error-t}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
class AuthService {
|
||||
async login(email, password) {
|
||||
const user = await this.userRepository
|
||||
.findByEmail(email);
|
||||
if (!user) {
|
||||
throw new AuthenticationError('Invalid credentials');
|
||||
}
|
||||
const valid = await bcrypt.compare(
|
||||
password, user.password
|
||||
);
|
||||
if (!valid) {
|
||||
throw new AuthenticationError('Invalid credentials');
|
||||
}
|
||||
// ...
|
||||
}
|
||||
}
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
|
||||
\vspace{0.2cm}
|
||||
|
||||
\begin{block}{Controller kezeli}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
app.post('/api/auth/login', async (req, res, next) => {
|
||||
try {
|
||||
const result = await authService.login(
|
||||
req.body.email, req.body.password);
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile]{Global Error Handler Middleware}
|
||||
\begin{block}{Központi hibaüzenet kezelés}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
app.use((error, req, res, next) => {
|
||||
console.error(error);
|
||||
|
||||
if (error.statusCode) {
|
||||
return res.status(error.statusCode)
|
||||
.json({ error: error.message });
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
error: 'Internal Server Error'
|
||||
});
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Service Layer Testing}
|
||||
\begin{block}{Unit Testing}
|
||||
A service-ek izoláltan tesztelhetők mock repository-kkal és service-ekkel.
|
||||
\end{block}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Előnyök:}
|
||||
\begin{itemize}
|
||||
\item Gyors tesztek (nincs adatbázis)
|
||||
\item Üzleti logika fókusz
|
||||
\item Mock-olható függőségek
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\item \textbf{Test framework-ök:}
|
||||
\begin{itemize}
|
||||
\item Jest
|
||||
\item Mocha + Chai
|
||||
\item Vitest
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\item \textbf{Mocking library-k:}
|
||||
\begin{itemize}
|
||||
\item Sinon.js
|
||||
\item Jest built-in mocks
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=5]{Service Unit Test példa}
|
||||
\begin{block}{AuthService.login() teszt}
|
||||
\small
|
||||
\begin{verbatim}
|
||||
describe('AuthService', () => {
|
||||
let authService, mockUserRepo;
|
||||
|
||||
beforeEach(() => {
|
||||
mockUserRepo = { findByEmail: jest.fn() };
|
||||
authService = new AuthService(mockUserRepo);
|
||||
});
|
||||
|
||||
test('login fails for invalid user', async () => {
|
||||
mockUserRepo.findByEmail.mockResolvedValue(null);
|
||||
await expect(
|
||||
authService.login('test@test.com', 'pass')
|
||||
).rejects.toThrow('Invalid credentials');
|
||||
});
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{block}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Service Layer Best Practices}
|
||||
\begin{enumerate}
|
||||
\item \textbf{Single Responsibility:} Egy service egy felelősségi kör
|
||||
\item \textbf{Dependency Injection:} Konstruktorban injektált függőségek
|
||||
\item \textbf{Thin Controllers:} Controller csak HTTP kezel
|
||||
\item \textbf{Error Handling:} Custom error osztályok használata
|
||||
\item \textbf{Async/Await:} Tiszta aszinkron kód
|
||||
\item \textbf{Validation:} Input validáció a service-ben is
|
||||
\item \textbf{Transaction Management:} Adatbázis tranzakciók
|
||||
\item \textbf{Logging:} Strukturált log-olás
|
||||
\end{enumerate}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Projekt struktúra Service Layer-rel}
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Fájl struktúra:}
|
||||
\small
|
||||
\begin{itemize}
|
||||
\item \texttt{src/}
|
||||
\begin{itemize}
|
||||
\item \texttt{controllers/}
|
||||
\begin{itemize}
|
||||
\item \texttt{auth.controller.js}
|
||||
\item \texttt{user.controller.js}
|
||||
\end{itemize}
|
||||
\item \texttt{services/}
|
||||
\begin{itemize}
|
||||
\item \texttt{auth.service.js}
|
||||
\item \texttt{user.service.js}
|
||||
\item \texttt{token.service.js}
|
||||
\end{itemize}
|
||||
\item \texttt{repositories/}
|
||||
\begin{itemize}
|
||||
\item \texttt{user.repository.js}
|
||||
\item \texttt{token.repository.js}
|
||||
\end{itemize}
|
||||
\item \texttt{models/}
|
||||
\item \texttt{middlewares/}
|
||||
\item \texttt{utils/}
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Rétegek felelősségei:}
|
||||
\begin{itemize}
|
||||
\item \textbf{Controller:}
|
||||
\begin{itemize}
|
||||
\item HTTP kérés/válasz
|
||||
\item Validáció (input)
|
||||
\item Service hívás
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\item \textbf{Service:}
|
||||
\begin{itemize}
|
||||
\item Üzleti logika
|
||||
\item Adatmanipuláció
|
||||
\item Orchestration
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\item \textbf{Repository:}
|
||||
\begin{itemize}
|
||||
\item Adatbázis műveletek
|
||||
\item Query-k
|
||||
\item ORM interaction
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Összefoglalás}
|
||||
\begin{itemize}
|
||||
\item \textbf{Service Layer} = Üzleti logika réteg
|
||||
\item \textbf{Separation of Concerns:} Controller, Service, Repository
|
||||
\item \textbf{AuthService:} Regisztráció, login, token kezelés
|
||||
\item \textbf{UserService:} Felhasználó kezelés, profil, jelszó
|
||||
\item \textbf{TokenService:} Token generálás, validálás, refresh
|
||||
\item \textbf{AuthorizationService:} Jogosultság ellenőrzés
|
||||
\item \textbf{Dependency Injection:} Konstruktor alapú DI
|
||||
\item \textbf{Error Handling:} Custom error osztályok
|
||||
\item \textbf{Testing:} Unit teszt mock-okkal
|
||||
\item \textbf{Best Practices:} Thin controller, fat service
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.3cm}
|
||||
|
||||
\begin{exampleblock}{Miért használjuk?}
|
||||
Tiszta kód, újrafelhasználhatóság, tesztelhetőség, karbantarthatóság
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
@@ -0,0 +1,280 @@
|
||||
\section{Session}
|
||||
|
||||
\begin{frame}{Mi az a session?}
|
||||
\begin{block}{Definíció}
|
||||
Szerver-oldali állapot, amely felhasználói adatokat tárol bejelentkezés után.
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Stateful} megközelítés
|
||||
\item Kliens csak \textbf{session ID}-t tárol (cookie)
|
||||
\item Tartalom a szerveren (memória/DB/Redis)
|
||||
\end{itemize}
|
||||
|
||||
\begin{alertblock}{Fontos}
|
||||
Session auth nem token-alapú, szerver kezeli az állapotot.
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Session vs Token}
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{block}{Session}
|
||||
\begin{itemize}
|
||||
\item Szerver tárolja
|
||||
\item Cookie-ban ID
|
||||
\item Könnyű visszavonás
|
||||
\item Nehezebb skálázás
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{column}
|
||||
|
||||
\begin{column}{0.48\textwidth}
|
||||
\begin{block}{Token (JWT)}
|
||||
\begin{itemize}
|
||||
\item Stateless
|
||||
\item Kliens tárolja
|
||||
\item Nehezebb visszavonás
|
||||
\item Könnyebb skálázás
|
||||
\end{itemize}
|
||||
\end{block}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
|
||||
\begin{center}
|
||||
Session $\leftrightarrow$ JWT: kontroll vs skálázhatóság
|
||||
\end{center}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Session folyamat}
|
||||
\begin{enumerate}
|
||||
\item Bejelentkezés (username + password)
|
||||
\item Adatok ellenőrzése
|
||||
\item \textbf{Session} létrehozás
|
||||
\item Session ID cookie-ban
|
||||
\item Kliens csatolja minden kéréshez
|
||||
\item Szerver ID alapján azonosít
|
||||
\end{enumerate}
|
||||
|
||||
\begin{alertblock}{Megjegyzés}
|
||||
HTTPS kötelező, különben session ID elliopható!
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Session tárolás}
|
||||
\begin{block}{Hol tároljuk?}
|
||||
Session adatokat szerveren, több lehetőség:
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{In-memory} - gyors, nem skálázható
|
||||
\item \textbf{Redis} - gyors, skálázható, TTL
|
||||
\item \textbf{DB} - tartós, lassabb
|
||||
\item \textbf{Distributed cache} - nagy rendszerekhez
|
||||
\end{itemize}
|
||||
|
||||
\begin{alertblock}{Best Practice}
|
||||
Élesben ne használj in-memory store-t!
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=15]{Redis Session Store - Telepítés}
|
||||
\begin{block}{Redis előnyei session tároláshoz}
|
||||
Gyors, skálázható, beépített TTL (Time To Live) támogatás
|
||||
\end{block}
|
||||
|
||||
\begin{verbatim}
|
||||
npm install express-session connect-redis redis
|
||||
|
||||
// Redis kliens és session store
|
||||
const session = require('express-session');
|
||||
const RedisStore = require('connect-redis').default;
|
||||
const { createClient } = require('redis');
|
||||
|
||||
const redisClient = createClient({
|
||||
host: 'localhost',
|
||||
port: 6379
|
||||
});
|
||||
redisClient.connect();
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=15]{Redis Session - Konfiguráció}
|
||||
\begin{verbatim}
|
||||
app.use(session({
|
||||
store: new RedisStore({ client: redisClient }),
|
||||
secret: process.env.SESSION_SECRET,
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: {
|
||||
httpOnly: true,
|
||||
secure: true, // HTTPS
|
||||
maxAge: 1000 * 60 * 60 * 24 // 1 nap
|
||||
}
|
||||
}));
|
||||
|
||||
app.post('/login', async (req, res) => {
|
||||
const user = await authenticateUser(req.body);
|
||||
req.session.userId = user.id;
|
||||
req.session.role = user.role;
|
||||
res.json({ success: true });
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=15]{Redis Session - Műveletek}
|
||||
\begin{verbatim}
|
||||
// Session olvasása
|
||||
app.get('/profile', (req, res) => {
|
||||
if (!req.session.userId) {
|
||||
return res.status(401).json({ error: 'Not logged in' });
|
||||
}
|
||||
res.json({ userId: req.session.userId });
|
||||
});
|
||||
|
||||
// Session módosítása
|
||||
app.post('/settings', (req, res) => {
|
||||
req.session.theme = req.body.theme;
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// Session törlése (logout)
|
||||
app.post('/logout', (req, res) => {
|
||||
req.session.destroy((err) => {
|
||||
if (err) return res.status(500).send('Error');
|
||||
res.clearCookie('connect.sid');
|
||||
res.json({ message: 'Logged out' });
|
||||
});
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=15]{Redis Session előnyei}
|
||||
\begin{columns}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Előnyök:}
|
||||
\begin{itemize}
|
||||
\item Villámgyors (in-memory)
|
||||
\item Automatikus TTL támogatás
|
||||
\item Skálázható (cluster mode)
|
||||
\item Persistence opcionális
|
||||
\item Pub/Sub támogatás
|
||||
\end{itemize}
|
||||
\end{column}
|
||||
\begin{column}{0.48\textwidth}
|
||||
\textbf{Használati esetek:}
|
||||
\begin{itemize}
|
||||
\item Session store
|
||||
\item Rate limiting
|
||||
\item Real-time analytics
|
||||
\item Cache layer
|
||||
\item Message queue
|
||||
\end{itemize}
|
||||
\end{column}
|
||||
\end{columns}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Session élettartam}
|
||||
\begin{block}{Session Timeout}
|
||||
Session-ök lejárnak idő vagy inaktivitás után.
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Absolute:} fix lejárat (pl. 24h)
|
||||
\item \textbf{Idle:} aktivitás hiánya
|
||||
\item \textbf{Sliding:} aktivitás meghosszabbítja
|
||||
\end{itemize}
|
||||
|
||||
\begin{exampleblock}{Példa}
|
||||
Online bank: 5 perc inaktivitás $\to$ kiléptetés.
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[fragile,shrink=10]{Express session példa}
|
||||
\begin{exampleblock}{express-session használata}
|
||||
\tiny
|
||||
\begin{verbatim}
|
||||
const session = require('express-session');
|
||||
|
||||
app.use(session({
|
||||
secret: 'super-secret-key',
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
maxAge: 1000 * 60 * 60 // 1 óra
|
||||
}
|
||||
}));
|
||||
|
||||
app.post('/login', (req, res) => {
|
||||
req.session.userId = user.id;
|
||||
res.json({ message: 'Logged in' });
|
||||
});
|
||||
\end{verbatim}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Cookie biztonság}
|
||||
\begin{block}{Mitől biztonságos?}
|
||||
Session ID cookie biztonságos beállításokkal.
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{HttpOnly}: JS nem fér hozzá
|
||||
\item \textbf{Secure}: csak HTTPS
|
||||
\item \textbf{SameSite}: CSRF védelem
|
||||
\item \textbf{Rotálás}: ID frissítés (fixation ellen)
|
||||
\end{itemize}
|
||||
|
||||
\begin{alertblock}{Session Fixation}
|
||||
Támadó előre beállított ID-t "ráerőltet" a felhasználóra.
|
||||
\end{alertblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}[shrink=5]{Session alapú autorizáció}
|
||||
\begin{block}{Jogosultságok session-ben}
|
||||
A session tárolhatja a felhasználó szerepköreit és jogosultságait.
|
||||
\end{block}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Példa:} \texttt{req.session.role = 'Admin'}
|
||||
\item Middleware ellenőrzi a role-t
|
||||
\item Ha nincs jogosultság: \texttt{403 Forbidden}
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.4cm}
|
||||
|
||||
\begin{exampleblock}{Minta ellenőrzés}
|
||||
\texttt{if (req.session.role !== 'Admin') return res.status(403);}
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Session Best Practices}
|
||||
\begin{itemize}
|
||||
\item HTTPS használata mindenhol
|
||||
\item HttpOnly + Secure + SameSite cookie beállítások
|
||||
\item Session store: Redis vagy DB
|
||||
\item Rövid idle timeout érzékeny rendszereknél
|
||||
\item Session ID rotálás bejelentkezéskor
|
||||
\item Rate limiting és brute force védelem
|
||||
\end{itemize}
|
||||
|
||||
\vspace{0.5cm}
|
||||
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Összefoglalás - Session}
|
||||
\begin{itemize}
|
||||
\item Session = szerver-oldali állapot (cookie csak ID)
|
||||
\item Kényelmes, de skálázásnál kihívás
|
||||
\item Cookie biztonsági beállítások kritikusak
|
||||
\item \textbf{Redis a leggyakoribb session store} - gyors, skálázható
|
||||
\item Timeout és rotáció védelem session támadások ellen
|
||||
\item Redis TTL automatikus session lejárathoz
|
||||
\end{itemize}
|
||||
|
||||
\begin{exampleblock}{Produkciós környezet}
|
||||
Redis cluster + persistence + backup = megbízható session kezelés
|
||||
\end{exampleblock}
|
||||
\end{frame}
|
||||
Reference in New Issue
Block a user