= Други теми = == Безбедност == === Спречување на SQL Injection === Користиме Entity Framework Core (ORM). EF Core автоматски ги параметризира сите LINQ прашања (queries). Ова спречува напаѓачите да вметнат злонамерни SQL команди преку полињата за внес. * EF Core го третира **username** како параметар (@p0), а не како извршлив код. * Ова спречува SQL Injection напади (на пр., ' OR 1=1 --). {{{ public async Task AuthenticateAsync(string username, string password) { var user = await _context.Users .FirstOrDefaultAsync(u => u.Username == username && u.IsActive); if (user == null) return null; bool isHashed = user.Password.StartsWith("$2") && user.Password.Length == 60; if (isHashed) { if (BCrypt.Net.BCrypt.Verify(password, user.Password)) return user; } else { if (user.Password == password) { user.Password = BCrypt.Net.BCrypt.HashPassword(password); await _context.SaveChangesAsync(); return user; } } return null; } }}} === Хеширање на лозинки (Заштита на податоци) === Лозинките се зачувуваат како хеш вредности со користење на алгоритмот BCrypt, а не како обичен текст. {{{ public async Task CreateUserAsync(User user, string password) { using var transaction = await _context.Database.BeginTransactionAsync(); try { user.Password = BCrypt.Net.BCrypt.HashPassword(password); _context.Users.Add(user); await _context.SaveChangesAsync(); await transaction.CommitAsync(); return true; } catch { await transaction.RollbackAsync(); return false; } } }}} === Безбедност на Database Context (Row-Level идентификација) === Го пренесуваме идентитетот на моментално најавениот корисник од Application Layer до Database Layer (PostgreSQL) користејќи Session Variables. Ова и овозможува на базата на податоци да знае кој ја извршува операцијата. {{{ public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) { var username = _httpContextAccessor.HttpContext?.User?.Identity?.Name ?? "system"; await Database.ExecuteSqlRawAsync("SELECT set_config('app.current_user', {0}, false)", new[] { username }, cancellationToken); return await base.SaveChangesAsync(cancellationToken); } }}} === Авторизација (Role-Based Access Control) === Го ограничуваме пристапот до Controllers и Actions со користење на атрибутот ** [Authorize] **. Само автентицирани корисници со валидни cookies можат да пристапат до овие ресурси. * Овој атрибут осигурува дека само најавени корисници можат да пристапат до било која акција во овој контролер. * Неавтентицираните барања се пренасочуваат кон страницата за најава (Login page). {{{ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace StockMaster.Controllers { [Authorize] public class ReportController : Controller { private readonly IReportService _reportService; public ReportController(IReportService reportService) { _reportService = reportService; } public IActionResult Index() { return View(); } // ... други акции } } }}} === Безбедност на база на податоци базирана на логика (Triggers) === Користиме Database Triggers за да спроведеме безбедносни правила што не можат да бидат заобиколени од апликацијата. Поточно, спречуваме корисник да ја избрише сопствената account за да обезбедиме стабилност на системот и следење на активности. {{{ CREATE OR REPLACE FUNCTION stock_management.prevent_self_delete() RETURNS TRIGGER AS $$ BEGIN IF OLD.username = current_setting('app.current_user', true) THEN RAISE EXCEPTION 'You cannot delete your own account.'; END IF; RETURN OLD; END; $$ LANGUAGE plpgsql; CREATE OR REPLACE TRIGGER trg_prevent_self_delete BEFORE DELETE ON stock_management.users FOR EACH ROW EXECUTE FUNCTION stock_management.prevent_self_delete(); }}} == Пеформанси - Индекси == За да се зголеми перформансата на базата на податоци, се применуваат стратегии за индексирање и се анализира споредбата на перформансите во состојби со индекс и без индекс. Тестовите се извршени со користење на командата EXPLAIN ANALYZE за реални мерења во реално време. Обем на тест податоци: 500.000+ редови (Табела за нарачки), 100.000+ производи (Табела за производи) {{{ SET search_path TO stock_management; TRUNCATE purchase_order, product RESTART IDENTITY CASCADE; -- 100.000 производи INSERT INTO product (name, description, sku, unit_price, category_id, supplier_id) SELECT 'Product ' || i, 'Desc', 'SKU-' || i, (random() * 100)::numeric(12,2), 1, 1 FROM generate_series(1, 100000) AS i; -- 500.000 нарачки INSERT INTO purchase_order (order_date, expected_delivery_date, status, supplier_id, warehouse_id) SELECT CURRENT_DATE, CURRENT_DATE + i, CASE WHEN i % 2 = 0 THEN 'Pending' ELSE 'Received' END, 1, 1 FROM generate_series(1, 500000) AS i; }}} === Сценарио 1: Листaње на нарачки во очекување === Цел: Персоналот во магацинот да ги гледа само нарачките чиј статус е '''Pending''' (Во очекување). Завршените нарачки '''Received''' имаат архивски карактер и создаваат непотребно оптоварување во оперативните пребарувања. {{{ SELECT * FROM purchase_order WHERE status = 'Pending'; EXPLAIN ANALYZE SELECT * FROM purchase_order WHERE status = 'Pending'; }}} ==== 1.1. Без индекс (Before Optimization) ==== '''Метод''': Parallel Seq Scan (секвенцијално пребарување). Базата на податоци мора да ги прочита сите 50.000 редови еден по еден. '''Времетраење: 173.154 ms''' '''Анализа:''' Многу бавно. [[Image(without_index_1.png, 800px)]] ==== 1.2. Стратегија за индексирање (Partial Index) ==== Наместо да се индексира целата табела, е креиран делумен индекс (Partial Index) кој ги вклучува само записите каде што status = 'Pending'. Ова го намалува големината на индексот и ја забрзува извршувањето на барањето. '''Применет индекс:''' {{{ CREATE INDEX idx_po_status_pending ON purchase_order(expected_delivery_date) WHERE status = 'Pending'; }}} ==== 1.3. Со индекс ==== '''Метод''': Index Scan. '''Времетраење: 94.976 ms ''' '''Анализа:''' Многу по брзо. [[Image(with_index_1.png, 800px)]] === Сценарио 2: Пребарување на производи === Цел: Корисникот да може да пребарува производи според името (на пример: производи што започнуваат со "Laptop"). {{{ SELECT * FROM product WHERE name LIKE 'Laptop%'; EXPLAIN ANALYZE SELECT * FROM product WHERE name LIKE 'Laptop%'; }}} ==== 2.1. Без индекс (Before Optimization) ==== '''Метод''': Seq Scan. Сите 10.000 редови со производи се проверени еден по еден со споредба на текст. '''Времетраење: 142.270 ms''' '''Анализа:''' Многу бавно. [[Image(without_index_2.png, 800px)]] ==== 2.2. Стратегија за индексирање (Partial Index) ==== За да се забрзаат текстуалните пребарувања, е применет B-Tree индекс на колоната '''name'''. Параметарот text_pattern_ops овозможува LIKE барањата да можат да го користат индексот. '''Применет индекс:''' {{{ CREATE INDEX idx_product_name ON product(name text_pattern_ops); }}} ==== 2.3. Со индекс ==== '''Метод''': Index Scan. '''Времетраење: 0.470 ms ''' '''Анализа:''' Многу по брзо. [[Image(with_index_2.png, 800px)]]