401 (Unauthorized) 와 403 (Forbidden)은 의미적으로 어떤 차이가 있나요?

401은 인증(Authentication) 실패, 403은 인가(Authorization) 실패를 의미하며, "너 누구야?"와 "너 누군지는 알지만 권한이 없어"의 차이입니다.

인증 vs 인가

인증 (Authentication): 신원 확인 → "이 사람이 누구인가?"
인가 (Authorization):  권한 확인 → "이 사람이 이 행동을 할 수 있는가?"

[비유]
건물 출입증이 없음      → 401 (인증 실패: 너 누구야?)
출입증은 있지만 3층 출입 불가 → 403 (인가 실패: 너 여기 못 들어가)

동작 차이

[401 Unauthorized]
클라이언트: GET /mypage (토큰/세션 없음)
서버:       "인증 정보가 없거나 유효하지 않습니다" → 401
           WWW-Authenticate 헤더로 인증 방법 안내
클라이언트: 로그인 후 재요청하면 성공 가능 O

[403 Forbidden]
클라이언트: GET /admin (일반 사용자 토큰으로 요청)
서버:       "인증은 확인했지만 접근 권한이 없습니다" → 403
클라이언트: 로그인을 다시 해도 소용없음 X → 권한 자체가 없기 때문
항목401 Unauthorized403 Forbidden
의미인증 실패인가 실패
서버가 사용자를 아는가모름알고 있음
재시도 시 해결 가능로그인하면 해결 가능권한이 부여되지 않는 한 불가
응답 헤더WWW-Authenticate 포함 권장별도 헤더 없음
대표 상황토큰 없음, 토큰 만료, 잘못된 자격 증명일반 유저가 관리자 API 접근

Spring Security 동작 흐름

요청 진입
  │
  ▼
인증 필터 (AuthenticationFilter)
  │
  ├─ 인증 정보 없음/유효하지 않음 → AuthenticationEntryPoint → 401
  │
  ▼
인가 필터 (AuthorizationFilter)
  │
  ├─ 권한 부족 → AccessDeniedHandler → 403
  │
  ▼
컨트롤러 도달 (정상 처리)
@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .requestMatchers("/mypage/**").authenticated()
                .anyRequest().permitAll()
            )
            // 401 처리 — 인증 실패
            .exceptionHandling(ex -> ex
                .authenticationEntryPoint((request, response, authException) -> {
                    response.setStatus(401);
                    response.getWriter().write("인증이 필요합니다");
                })
                // 403 처리 — 인가 실패
                .accessDeniedHandler((request, response, accessDeniedException) -> {
                    response.setStatus(403);
                    response.getWriter().write("접근 권한이 없습니다");
                })
            );
        return http.build();
    }
}

실무에서 흔히 혼동하는 상황

상황올바른 코드이유
토큰 없이 API 요청401인증 자체가 안 됨
만료된 토큰으로 요청401유효한 인증 정보가 아님
일반 유저가 관리자 API 요청403인증은 됐지만 권한 부족
자기 것이 아닌 타인의 리소스 접근403인증은 됐지만 해당 리소스에 권한 없음
비활성화된 계정으로 요청403인증은 됐지만 계정이 차단 상태

타인의 리소스 접근 예시

@GetMapping("/orders/{orderId}")
public ResponseEntity<Order> getOrder(@PathVariable Long orderId,
                                       @AuthenticationPrincipal UserDetails user) {
    Order order = orderService.findById(orderId);

    // 인증은 됐지만 본인 주문이 아닌 경우 → 403
    if (!order.getMemberId().equals(user.getId())) {
        throw new AccessDeniedException("본인의 주문만 조회할 수 있습니다");
    }

    return ResponseEntity.ok(order);
}

보안 관점에서의 고민: 403 대신 404를 쓰는 경우

GET /admin/users → 403 Forbidden
→ 공격자: "이 URL이 존재하는구나, 관리자 페이지가 있구나" ← 정보 노출

GET /admin/users → 404 Not Found
→ 공격자: "이 URL은 없나 보다" ← 존재 자체를 숨김

보안이 중요한 서비스에서는 권한 없는 리소스에 대해 의도적으로 404를 반환하여 리소스 존재 여부를 숨기기도 합니다. GitHub가 대표적인 사례로, 접근 권한이 없는 Private 저장소에 접근하면 403이 아닌 404를 반환합니다.

면접 포인트

CSLOG에서 보기