550 lines
15 KiB
TeX
550 lines
15 KiB
TeX
\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}
|