Перейти к основному содержимому

Безопасность и авторизация

Механизм аутентификации

Система использует JWT (JSON Web Token) с библиотекой JJWT. Сессии — stateless: сервер не хранит состояние сессии, каждый запрос содержит токен.

Жизненный цикл токена

1. POST /api/v1/auth/login
Body: { email, password }

→ AuthServiceImpl.authenticate()
→ AuthenticationManager.authenticate() # проверка пароля (BCrypt)
→ JwtService.generateToken(UserDetails) # создание JWT

Response: { token: "eyJ...", user: { id, name, email, role } }

2. Клиент сохраняет токен:
localStorage.setItem('dm_shop_token', token)

3. Axios Interceptor (src/lib/api.ts):
config.headers.Authorization = `Bearer ${token}`

4. JwtAuthFilter (Spring Security Filter Chain):
→ Читает заголовок Authorization
→ JwtService.extractUsername(token)
→ UserDetailsService.loadUserByUsername(email)
→ SecurityContextHolder.setAuthentication(authToken)

Параметры JWT

ПараметрЗначениеПеременная окружения
Алгоритм подписиHMAC-SHA256
Секретный ключ64-байтный hexJWT_SECRET
Время жизни24 часа (86 400 000 мс)JWT_EXPIRATION_MS
Хранение на клиентеlocalStorage

Роли пользователей

Пять ролей определены в UserRole (USER, EDITOR, MANAGER, ADMIN, SUPER_ADMIN):

РольОписание
USERЗарегистрированный покупатель: просмотр, оформление заказов, личный кабинет
EDITORКонтент-редактор: баннеры, преимущества, отзывы, медиатека, категории (чтение/настройка)
MANAGERВсё, что у EDITOR + заказы и заявки с сайта
ADMINПолный доступ ко всем разделам /api/v1/admin/**, товарам, пользователям
SUPER_ADMINТо же, что ADMIN + управление прайсами и пополнениями лицензии

:::note Роль MANAGER ранее называлась MODERATOR Переименование выполнено миграцией 051-rename-moderator-to-manager. :::

Spring Security сопоставляет роли с authority ROLE_<NAME>. Доступ настроен через .hasAnyRole(...), поэтому проверка активна — запросы без нужной роли получают 401/403.

Правила доступа

Публичные эндпоинты (без токена)

/api/v1/auth/** (любой метод)
/swagger-ui/**, /v3/api-docs/** (GET)
/ws/** (WebSocket)
OPTIONS /api/** (CORS preflight)
GET /api/v1/config/**
GET /api/v1/products, /api/v1/products/*, /api/v1/products/*/attributes
GET /api/v1/categories/homepage, /api/v1/categories/tree
GET /api/v1/brands/**
GET /api/v1/reviews/active
GET /api/v1/banner-blocks/active
GET /api/v1/feature-items/active
GET /api/v1/images/*
POST /api/v1/cart/validate, /api/v1/orders, /api/v1/contacts, /api/v1/config/batch

Только авторизованные (любая роль)

GET /api/v1/orders/my

Профиль и смена пароля (PUT /api/v1/auth/profile, /change-password) выполняются под токеном текущего пользователя через эндпоинты /api/v1/auth/**.

Контент: EDITOR, MANAGER, ADMIN, SUPER_ADMIN

/api/v1/categories/** (чтение и запись)
/api/v1/images/** (загрузка/замена/удаление)
/api/v1/reviews/** (включая export/import)
/api/v1/banner-blocks/**
/api/v1/feature-items/**

Продажи: MANAGER, ADMIN, SUPER_ADMIN

GET/PATCH/POST /api/v1/admin/orders/** (кроме DELETE)
/api/v1/admin/contacts/**

Товары и весь /admin: ADMIN, SUPER_ADMIN

POST/PUT/PATCH/DELETE /api/v1/products/**
DELETE /api/v1/admin/orders/** (удаление заказов)
GET /api/v1/admin/orders/bulk-delete/preview
/api/v1/admin/** (пользователи, конфиг, синхронизация, XML-пайплайн, лицензия и т.д.)

Только SUPER_ADMIN

Управление прайсами и пополнениями лицензии (POST/PUT/DELETE /api/v1/admin/license/prices/**, /topups/**) проверяется на уровне сервиса.

Все правила заданы в SecurityConfig.securityFilterChain() через .hasAnyRole(...); финальное правило — .anyRequest().authenticated(). Неавторизованный запрос получает 401 с телом {"error":"Unauthorized"}.

Конфигурация Spring Security

// SecurityConfig.java — ключевые настройки
http
.csrf(AbstractHttpConfigurer::disable) // REST API — CSRF не нужен
.sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/auth/**").permitAll()
// ... публичные GET/POST ...
.requestMatchers(HttpMethod.DELETE, "/api/v1/admin/orders/**")
.hasAnyRole("ADMIN", "SUPER_ADMIN")
.requestMatchers(HttpMethod.GET, "/api/v1/admin/orders/**")
.hasAnyRole("ADMIN", "SUPER_ADMIN", "MANAGER")
.requestMatchers("/api/v1/reviews/**", "/api/v1/banner-blocks/**",
"/api/v1/feature-items/**", "/api/v1/images/**",
"/api/v1/categories/**")
.hasAnyRole("ADMIN", "SUPER_ADMIN", "EDITOR", "MANAGER")
.requestMatchers("/api/v1/admin/**").hasAnyRole("ADMIN", "SUPER_ADMIN")
.anyRequest().authenticated()
);

Хеширование паролей

Используется BCryptPasswordEncoder (стандартный Spring Security). Пароли в базе данных хранятся только в виде bcrypt-хешей, не в открытом виде.

CORS

Настраивается в WebConfig через свойство cors.allowed-origins:

# application-local.yaml
cors:
allowed-origins: http://localhost:3000

# Production
cors:
allowed-origins: https://shop.example.com

Разрешены методы: GET, POST, PUT, PATCH, DELETE, OPTIONS.
Разрешены все заголовки (*).
Разрешены credentials.

Регистрация и профиль

POST /api/v1/auth/register
Body: { name, email, password }
→ Проверка уникальности email (DuplicateEmailException если занят)
→ BCrypt.encode(password)
→ UserEntity.role = USER
→ Возвращает JWT токен сразу

PUT /api/v1/auth/profile
Body: { name, phone }
→ Требует токен (текущий пользователь)

PUT /api/v1/auth/change-password
Body: { currentPassword, newPassword }
→ Проверяет currentPassword через BCrypt.matches()