본문 바로가기
트러블 슈팅 및 도입기

401 vs 403 인증과 권한의 미묘한 차이

by ernest45 2023. 10. 20.

 

 

1. 401 Unauthorized:

 
 
 
401 Unauthorized는 클라이언트가 **인증(Authentication)**을 하지 않았거나 실패 했을 때
 
401의 특징
 
의미: 인증 실패 또는 인증 정보 없음
 
 
응답 :
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example"
Content-Type: application/json
{"error": "Invalid or missing token"}
 

 

 

togedog에서의 401 사례

 
 

 

 

 

 

1.JWT 검증 실패
 
JwtService.isTokenValidfalse 반환
public boolean isTokenValid(String token) {
    try {
        JWT.require(Algorithm.HMAC512(secretKey)).build().verify(token);
        return true;
    } catch (Exception e) {
        log.error("유효하지 않은 토큰입니다. {}", e.getMessage());
        return false;
    }
}

 이래서 access가 403을 내려줬다.

 

 

 
2.JwtAuthenticationProcessingFilter에서 Access Token이 없거나 유효하지 않으면 401
 
if (refreshToken == null) {
    checkAccessTokenAndAuthentication(request, response, filterChain);
    // 유효하지 않은 Access Token → 401
}

 

 

3.로그인 실패 시 401 반환

-후에 설명할 403을 명확하게 내리는 것 보다 ok를 내리는 게 더 좋다고 한다.-

 

 

2. 403 Forbidden

 
403 Forbidden은 클라이언트가 인증은 성공했지만,
요청한 리소스에 대한 **권한(Authorization)**이 없어서 접근이 거부된 경우 발생.
403의 특징
 
의미: 권한 부족
 
 
응답
 
HTTP/1.1 403 Forbidden
Content-Type: application/json
{"error": "You do not have permission to access this resource"}
 
 
 
 
togedog에서의 403 사례
 

 

 

 

 

 
 
 
 
2. 로그인한 member와 작성자가 다를 때
 
 

 

3. 401 vs 403 에 대한 고찰

401과 403은 비슷해 보이지만, **인증(Authentication)**과 **권한(Authorization)**이라는 다른 개념!
 
 
 
 
 
 

글을 쓴 이유 -

 

403 Forbidden

  • 403은 인증된 사용자의 잘못된 접근을 차단해 데이터 무결성과 기밀성을 갖지만
    403 응답이 특정 리소스의 존재를 암시할 수 있음 (예: "이 게시물에 권한 없음" → 게시물 존재 확인).
  • 공격자가 403을 통해 시스템 구조를 탐색 가능

 

개선 사항 - 

 

현재 어떤 토큰 만료 시 무조건 403 응답에서

401과 403을 섞어서 응답

 

  • /refresh 호출 → Access Token 만료 → 401 → Refresh Token 검증
  •  
  • Refresh Token 유효 → 새 토큰 발급.
  • 권한 없는 리소스 접근 → 403

 

 

 

 

4. GitHub의 403 → 404 반환:

 

 
 
401과 403을 다룰 때, GitHub의 독특한 보안 관행은 주목할 만해요. GitHub는 특정 리소스에 대한 403 Forbidden 응답 대신 404 Not Found를 반환하는 경우가 많아요. 이건 단순한 실수가 아니라, 보안 강화를 위한 의도적인 설계예요.
왜 403 대신 404를 반환하나?
  • 정보 노출 최소화:
    • 403은 "리소스는 존재하지만 너에겐 권한 없어"를 의미 → 공격자에게 리소스의 존재를 확인시켜줘.
    • 예: https://github.com/private-repo에 접근 시 403 → "private-repo가 존재한다"는 정보 노출.
    • 404는 "그런 리소스 없어"로 응답 → 리소스 존재 여부를 숨김.
  • 공격 탐색 방지:
    • 공격자가 403 응답을 통해 private 리포지토리, 숨겨진 엔드포인트 등을 추측 가능.
    • 404로 통일하면 공격자가 시스템 구조를 파악하기 어려워.
  • 예시:
    • GitHub에서 비공개 리포지토리 접근 시:
      http
       
      HTTP/1.1 404 Not Found
      {"message": "Not Found"}
 
  • 공격자가 특정 사용자의 프로필 존재 여부를 알 수 없음
  • 주의점:
    • 404 남용은 디버깅 어렵게 해 → 서버 로그에 실제 에러(403) 기록:
       
      log.warn("Unauthorized access attempt to userId={} by {}", userId, principal.getEmail());

 

 

404에 대해 내부적으로만 error를 잘 처리할 수 있고, 404과 403을 섞은 이유를 사용자가 납득하고 많은 사람들이 알 수 있다면

너무 좋은 전략인 것 같다!