wiki:OtherTopics

Version 18 (modified by 221181, 3 weeks ago) ( diff )

--

Други теми

Безбедност

Спречување на SQL Injection

Користиме Entity Framework Core (ORM). EF Core автоматски ги параметризира сите LINQ прашања (queries). Ова спречува напаѓачите да вметнат злонамерни SQL команди преку полињата за внес.

  • EF Core го третира username како параметар (@p0), а не како извршлив код.
  • Ова спречува SQL Injection напади (на пр., ' OR 1=1 --).
public async Task<User> 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<bool> 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<int> 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 за реални мерења во реално време.

Сценарио 1: Тековен залиха по складиште

Цел: Прикажува вкупниот број производи и вредност на залихата по складишта.

SELECT
    w.warehouse_id,
    w.name AS warehouse_name,
    SUM(ws.quantity_on_hand) AS total_units,
    SUM(ws.quantity_on_hand * p.unit_price) AS total_stock_value
FROM warehouse_stock ws
JOIN warehouse w ON ws.warehouse_id = w.warehouse_id
JOIN product p ON ws.product_id = p.product_id
GROUP BY w.warehouse_id, w.name
ORDER BY total_stock_value DESC;

1.1. Без индекс

Кога се поврзува табелата warehouse_stock со табелата product преку колоната product_id (JOIN), ако нема индекс, базата на податоци ќе ги скенира сите редови.

"HashAggregate  (cost=705.70..706.95 rows=100 width=262) (actual time=102.460..102.484 rows=3.00 loops=1)"
"  Group Key: w.warehouse_id"
"  Batches: 1  Memory Usage: 32kB"
"  Buffers: shared hit=164 dirtied=1"
"  ->  Hash Join  (cost=191.75..518.20 rows=15000 width=232) (actual time=4.965..78.081 rows=15000.00 loops=1)"
"        Hash Cond: (ws.product_id = p.product_id)"
"        Buffers: shared hit=164 dirtied=1"
"        ->  Hash Join  (cost=12.25..299.29 rows=15000 width=230) (actual time=0.282..37.808 rows=15000.00 loops=1)"
"              Hash Cond: (ws.warehouse_id = w.warehouse_id)"
"              Buffers: shared hit=97 dirtied=1"
"              ->  Seq Scan on warehouse_stock ws  (cost=0.00..246.00 rows=15000 width=12) (actual time=0.103..20.064 rows=15000.00 loops=1)"
"                    Buffers: shared hit=96"
"              ->  Hash  (cost=11.00..11.00 rows=100 width=222) (actual time=0.068..0.069 rows=3.00 loops=1)"
"                    Buckets: 1024  Batches: 1  Memory Usage: 9kB"
"                    Buffers: shared hit=1 dirtied=1"
"                    ->  Seq Scan on warehouse w  (cost=0.00..11.00 rows=100 width=222) (actual time=0.029..0.032 rows=3.00 loops=1)"
"                          Buffers: shared hit=1 dirtied=1"
"        ->  Hash  (cost=117.00..117.00 rows=5000 width=10) (actual time=4.433..4.444 rows=5000.00 loops=1)"
"              Buckets: 8192  Batches: 1  Memory Usage: 279kB"
"              Buffers: shared hit=67"
"              ->  Seq Scan on product p  (cost=0.00..117.00 rows=5000 width=10) (actual time=0.044..1.930 rows=5000.00 loops=1)"
"                    Buffers: shared hit=67"
"Planning:"
"  Buffers: shared hit=86"
"Planning Time: 26.724 ms"
"Execution Time: 103.080 ms"

Метод: Parallel Seq Scan (секвенцијално пребарување). Базата на податоци мора да ги прочита сите 500.000 редови еден по еден.

Времетраење: 173.154 ms

Анализа: Многу бавно.

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

Анализа: Многу по брзо.

Сценарио 2: Пребарување на производи

Цел: Корисникот да може да пребарува производи според името (на пример: производи што започнуваат со "Laptop").

SELECT * FROM product WHERE name LIKE 'Laptop%';
EXPLAIN ANALYZE SELECT * FROM product WHERE name LIKE 'Laptop%';

2.1. Без индекс

Метод: Seq Scan. Сите 100.000 редови со производи се проверени еден по еден со споредба на текст.

Времетраење: 142.270 ms

Анализа: Многу бавно.

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

Анализа: Многу по брзо.

Attachments (4)

Download all attachments as: .zip

Note: See TracWiki for help on using the wiki.