authn_z
This commit is contained in:
@@ -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}
|
||||
Reference in New Issue
Block a user