Безопасность и авторизация
Механизм аутентификации
Система использует 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-байтный hex | JWT_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()