556 lines
14 KiB
TeX
556 lines
14 KiB
TeX
\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}
|