В чому різниця між 401 та 403
Різниця між статусами 401 Unauthorized та 403 Forbidden полягає у фундаментальному розмежуванні двох понять безпеки: Аутентифікації (Authentication) та Авторизації (Authorization).
🟢 Junior Level
Різниця між статусами 401 Unauthorized та 403 Forbidden полягає у фундаментальному розмежуванні двох понять безпеки: Аутентифікації (Authentication) та Авторизації (Authorization).
Просте формулювання
401 Unauthorized(Проблема аутентифікації) — «Хто ви? Я вас не знаю»: Сервер не зміг встановити особу клієнта. Запит надіслано без облікових даних або з недійсними/простроченими даними (токен, логін/пароль).403 Forbidden(Проблема авторизації) — «Я знаю, хто ви, але вам сюди не можна»: Особу клієнта успішно встановлено (користувач увійшов у систему), проте у нього немає прав (повноважень, ролей) для доступу до даного ресурсу.
Порівняльна аналогія з життя
- Випадок 401: Ви намагаєтеся зайти в бізнес-центр, але забули перепустку вдома. Охоронець на вході зупиняє вас: «Покажіть документ, що посвідчує вашу особу».
- Випадок 403: Ви показали охоронцю перепустку працівника бухгалтерії, але намагаєтеся відчинити двері серверної кімнати. Охоронець вас упізнав, але каже: «У вас немає допуску до серверної».
Поведінка клієнта
- У разі отримання 401: Клієнтський застосунок намагається оновити сесію (наприклад, через Refresh Token) або перенаправляє користувача на форму входу.
- У разі отримання 403: Повторний логін з тими самими обліковими даними марний. Застосунок показує повідомлення: «Доступ обмежено адміністратором».
🟡 Middle Level
Термінологічна плутанина у стандарті HTTP
У специфікації HTTP присутній історичний лінгвістичний дефект:
- Код названо
401 Unauthorized, хоча фактично він означаєUnauthenticated(відсутність підтвердженої автентичності особи). - Справжня авторизація (перевірка прав доступу) виражається кодом
403 Forbidden.
Протокольні вимоги (RFC 9110)
| Критерій | 401 Unauthorized (RFC 9110 §15.5.2) |
403 Forbidden (RFC 9110 §15.5.4) |
|---|---|---|
| Причина | Відсутні або невірні облікові дані | Недостатньо прав, повноважень або обмеження IP/CORS |
| Обов’язковий заголовок | Зобов’язаний містити заголовок WWW-Authenticate |
Заголовок WWW-Authenticate не передається |
| Повтор запиту | Має сенс після надання валідних облікових даних | Повтор запиту з тими самими даними заборонено |
| OAuth 2.0 / JWT | Токен відсутній, пошкоджений або прострочений (Expired) | Токен валідний, але не містить потрібного scope чи ролі |
Приклад заголовка WWW-Authenticate у разі 401:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token", error_description="The access token expired"
Content-Type: application/problem+json
Архітектура Spring Security 6.x
В екосистемі Spring Security поділ на 401 та 403 закладено на рівні базових інтерфейсів ланцюжка фільтрів (SecurityFilterChain):
HTTP Request
│
┌────────────┴────────────┐
▼ ▼
Неаутентифікований? Аутентифікований?
(AuthenticationException) (AccessDeniedException)
│ │
▼ ▼
AuthenticationEntryPoint AccessDeniedHandler
│ │
▼ ▼
HTTP 401 Unauthorized HTTP 403 Forbidden
AuthenticationEntryPoint: Перехоплює виняткиAuthenticationException(анонімний користувач намагається отримати захищений ресурс).AccessDeniedHandler: ПерехоплюєAccessDeniedException(користувач увійшов, але метод анотований@PreAuthorize("hasRole('ADMIN')"), а в користувача лише рольROLE_USER).- CSRF Token: Помилка перевірки CSRF-токена в Spring Security за замовчуванням генерує 403 Forbidden, а не 401!
🔴 Senior Level
Безпека vs Витік метаданих (IDOR та Information Disclosure)
На рівні архітектури Senior інженер зобов’язаний враховувати вектор атак User / Resource Enumeration: Якщо зловмисник перебирає ідентифікатори закритих документів:
- Запит
GET /api/v1/documents/999повертає404 Not Found. - Запит
GET /api/v1/documents/1000повертає403 Forbidden.
Відповідь 403 Forbidden миттєво повідомляє зловмиснику: документ з ID 1000 реально існує в системі, і до нього можна підбирати вектори атаки (IDOR).
Senior-патерн (Security by Obscurity / Silent Not Found):
У мультитенентних системах (Multi-tenant SaaS) та приватних API, якщо в поточного користувача немає прав на ресурс іншої організації, система повертає 404 Not Found, а не 403 Forbidden. Це гарантує, що чужий ресурс виглядає неіснуючим.
Production-ready налаштування Spring Security 6 + RFC 9457 ProblemDetail
package com.example.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ProblemDetail;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import java.io.IOException;
import java.net.URI;
import java.time.Instant;
@Configuration
@EnableMethodSecurity
public class SecurityConfiguration {
private final ObjectMapper objectMapper;
public SecurityConfiguration(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/public/*").permitAll()
.requestMatchers("/api/v1/admin/*").hasRole("ADMIN")
.anyRequest().authenticated()
)
.exceptionHandling(ex -> ex
// Точка входу для неаутентифікованих запитів (401)
.authenticationEntryPoint(customAuthenticationEntryPoint())
// Обробник для відмови в доступі (403)
.accessDeniedHandler(customAccessDeniedHandler())
);
return http.build();
}
@Bean
public AuthenticationEntryPoint customAuthenticationEntryPoint() {
return (request, response, authException) -> {
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
HttpStatus.UNAUTHORIZED,
"Full authentication is required to access this resource"
);
problem.setTitle("Unauthorized");
problem.setType(URI.create("https://api.example.com/errors/unauthorized"));
problem.setProperty("timestamp", Instant.now());
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_PROBLEM_JSON_VALUE);
response.setHeader("WWW-Authenticate", "Bearer realm=\"api\"");
objectMapper.writeValue(response.getOutputStream(), problem);
};
}
@Bean
public AccessDeniedHandler customAccessDeniedHandler() {
return (request, response, accessDeniedException) -> {
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
HttpStatus.FORBIDDEN,
"You do not have the required permissions to perform this operation"
);
problem.setTitle("Forbidden");
problem.setType(URI.create("https://api.example.com/errors/forbidden"));
problem.setProperty("timestamp", Instant.now());
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType(MediaType.APPLICATION_PROBLEM_JSON_VALUE);
objectMapper.writeValue(response.getOutputStream(), problem);
};
}
}
Відмінність OAuth 2.0 Scopes vs Application Roles під час 403 помилок
У розподілених мікросервісах із токенами JWT важливо розрізняти причину отримання 403:
- Недостатньо Role: Користувач аутентифікований як оператор (
ROLE_OPERATOR), але ендпоінт вимагаєROLE_SUPERVISOR. - Недостатньо OAuth Scope: Сторонній застосунок аутентифікувався токеном від імені користувача, проте користувач під час надання згоди (Consent Screen) дозволив доступ лише на
scope: "read". Спроба надсиланняPOSTвикличе відмову черезinsufficient_scopeзі статусом 403.
🎯 Шпаргалка для інтерв’ю
30-секундна відповідь
401 Unauthorizedсигналізує про збій аутентифікації: сервер не знає клієнта. Токен відсутній, некоректний або прострочений. За специфікацією RFC 9110 відповідь зобов’язана містити заголовокWWW-Authenticate.403 Forbiddenсигналізує про збій авторизації: сервер успішно встановив особу клієнта, проте у нього недостатньо прав, ролей або скоупів для виконання дії. Повторний виклик з тими самими обліковими даними марний.- У Spring Security за 401 відповідає
AuthenticationEntryPoint, а за 403 —AccessDeniedHandler.- З метою безпеки (захист від перебору IDOR) замість 403 часто повертають 404 Not Found, щоб приховати сам факт існування закритого ресурсу.
4 каверзних питання з відповідями
-
Який код відповіді має повернути сервер, якщо термін дії JWT токена минув (Expired)? Відповідь: Суворо
401 Unauthorized(із заголовкомWWW-Authenticate: Bearer error="invalid_token", error_description="The token expired"). Відповідь 403 була б архітектурною помилкою, оскільки мобільний або вебклієнт використовує саме 401 для прозорого запуску Refresh Token Flow. -
Чому за специфікацією RFC 9110 код називається “Unauthorized”, якщо він стосується саме аутентифікації? Відповідь: Це історична термінологічна неточність, якої припустилися на етапі створення протоколу HTTP в 1990-х роках. Семантично 401 означає
Unauthenticated, а справжня перевірка авторизації (прав доступу) покладена на403 Forbidden. -
У якому випадку за наявності валідної аутентифікації адміністратора запит завершиться кодом 403 Forbidden у Spring Security? Відповідь: Наприклад, за відсутності або невалідності CSRF-токена (у stateful додатках), у разі обмеження доступу за IP (IP filtering/WAF), або якщо JWT токен видано сторонньому застосунку без обов’язкового OAuth
scope. -
Навіщо в архітектурі Multi-tenant SaaS навмисно замінювати 403 Forbidden на 404 Not Found? Відповідь: Для запобігання витоку інформації (Information Disclosure) та атак класу IDOR. Код 403 підтверджує зловмиснику факт існування закритого ресурсу в системі. Код 404 повністю приховує присутність ресурсу.
Червоні прапорці (чого категорично не можна говорити)
- ❌ «401 і 403 — це одне й те саме, різниці немає» (один про аутентифікацію, другий про права доступу).
- ❌ «Якщо JWT токен прострочений, потрібно повертати 403 Forbidden» (ломає логіку автоматичного оновлення токенів у клієнтських застосунках).
- ❌ «Якщо у користувача 403 помилка, йому просто потрібно ще раз ввести логін і пароль» (повторний логін не надає нових прав доступу).
- ❌ «Відповідь 401 не потребує жодних обов’язкових заголовків» (за RFC 9110 заголовок
WWW-Authenticateсуворо обов’язковий).