401( Unauthorized) VS 403(Forbidden) HTTP 상태 비교

wooSim·2023년 6월 8일
2

1. HTTP 상태 코드 401(Unauthorized)과 403(Forbidden)


□ HTTP 상태 코드 4XX 오류

HTTP 상태 코드 중 4XX, 즉 4백번 대로 시작하는 코드는 Client error라는 뜻이다. 즉, 오류의 원인이 클라이언트에게 있다는 뜻으로 클라이언트가 이미 잘못된 요청(request)을 보냈기 때문에 똑같은 시도를 해도 계속 실패하게 된다.

□ HTTP 상태 코드 401(Unauthorized)

그 중에서도 401(Unauthorized)는 클라이언트가 인증되지 않아 정상적으로 요청을 처리할 수 없고 해당 리소스에 대한 인증이 필요하다는 의미의 상태코드이다.
401 코드를 응답받는 대표적인 경우는 로그인을 하지 않고 특정 리소스를 요청하는 경우이다.

□ HTTP 상태 코드 403(Forbidden)

이와 다르게 403(Forbidden)에러 코드는 서버가 클라이언트의 요청을 이해했지만 승인을 거부한 상태로 주로 인증 자격은 있지만 접근 권한이 없는 경우를 알려준다. 예를들어 일반 사용자가 로그인을 하여 인증은 되었지만 접근 권한이 없는 admin(관리자)등급의 리소스를 요청하는 경우이다.





2. 401(Unauthorized)와 403(Forbidden) 상태 사용 예시


해당 예제 화면과 소스는 쓰니가 "스프링 부트와 AWS로 혼자 구현하는 웹 서비스" 책을 학습하며 따라한 예제입니다. 😅
또 참고하실 사항으로 예제 코드에서 spring security 관련 코드에서 상속받아 사용한 WebSecurityConfigurerAdapter가 deprecated 되었고 SecurityFilterChain bean을 생성하여 설정하게 되었다.

□ 스프링 시큐리티에서 인증과 권한 설정

먼저 gradle에 OAuth2 의존성을 추가합니다. spring-boot-starter-oauth2-client는 구글, 네이버 등 소셜 로그인을 통한 인증과 권한처리를 쉽게 할 수 있게 해준다.

implementation('org.springframework.boot:spring-boot-starter-oauth2-client')

이제 해당 시스템은 스프링 시큐리티에 의해 자동 설정이 적용되어 모든 요청에 대해서 인증을 필요로 하게 됩니다.(일부 url을 허용하기 위해선 WebSecurityConfigurerAdaptre를 구현해야 합니다.)


아래 사진은 WebSecurityConfigurerAdapter를 통한 인증과 권한 설정 코드이다.

해당 코드에 대해 간단히 설명하자면

  1. h2-console 화면을 사용하기 위해 해당 옵션들을 disable 합니다.
  2. authorizeRequests 가 선언둬어야만 antMatchers 옵션을 사용할 수 있습니다.
  3. "/", "/css/**" 등 지정된 URL 들은 permitAll()옵션을 통해 전체 열람 권한을 주었습니다.
  4. "/api/v1/**" 주소를 가진 API는 USER 권한을 가진 사람만 가능하도록 했습니다.
  5. anyRequest(): 설정된 값들이외 나머지 url, authenticated(): 나머지 url들은 로그인한사용자에게만 허용
  6. 로그아웃 기능에 대한 설정의 진입점 / 로그아웃 성공시 "/"주소로 이동
  7. OAuth2Login 로그인 기능에 대한 여러 설정의 진입점
  8. 소셜 로그인 성공 시 후속조치를 진행할 UserService 인터페이싀의 구현체를 등록

※ @EnableWebSecurity 애너테이션은 모든 요청 URL이 스프링 시큐리티에 설정한 제어를 받도록 한다.


여기서 주목할 코드는 빨간색 네모 박스로 표시한 코드이다. 스프링 시큐리티는 ExceptionHandling 을 통해 인증, 인가 과정에서 발생한 예외를 처리할 수 있습니다.

  • AuthenticationEntryPoint : 인증 예외처리, 401(Unauthorized) 상태 코드 전달
  • AccessDeniedHandler : 권한(인가) 예외처리, 403(Forbidden) 상태 코드 전달
private final AuthenticationEntryPoint unauthorizedEntryPoint =
            (request, response, authException) -> {
                ErrorResponse fail = new ErrorResponse(HttpStatus.UNAUTHORIZED, "Spring security unauthorized...");
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
                String json = new ObjectMapper().writeValueAsString(fail);
                response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                PrintWriter writer = response.getWriter();
                writer.write(json);
                writer.flush();
            };
            
private final AccessDeniedHandler accessDeniedHandler =
            (request, response, accessDeniedException) -> {
                ErrorResponse fail = new ErrorResponse(HttpStatus.FORBIDDEN, "Spring security forbidden...");
                response.setStatus(HttpStatus.FORBIDDEN.value());
                String json = new ObjectMapper().writeValueAsString(fail);
                response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                PrintWriter writer = response.getWriter();
                writer.write(json);
                writer.flush();
            };

인증과 인가를 담당하는 AuthenticationEntryPoint, AccessDeniedHandler 함수에 각 예외의 후속 처리 작업을 해주었다.


□ 화면 예시

▷ 401(Unauthorized) 상태 응답 예시]
위의 configure() 함수의 antMatchers.("/", "/css/**",...).permitAll()로 인해 localhost8080(url: /)은 모든 request에 대해 열람을 할 수 있다.

하지만 여기서 글 등록 버튼을 클릭할 경우 "/posts/save" URL로 이동을 서버에 요청하는데 이는 전체 열람이 허용된 요청이 아니므로 아직 소셜 로그인 하지않은 사용자는 401(Unauthorized) 오류 코드를 응답받게 된다.


▷ 403(Forbidden) 상태 응답 예시
이번에는 구글 로그인 후 글 등록을 해보겠습니다.

로그인을 하면 DB에는 기본으로 "GUEST" ROLE로 들어가 있습니다.

여기서 글 등록 화면으로 이동 후 글을 작성하면 "GUEST"는 해당 글 등록 요청에 대한 권한이 없기 때문에 403(Forbidden) 오류 코드를 응답 받게 된다.

⚡ 해당 alert에 노출된 responseText 내용을 보면 AccessDeniedHandler 함수에서 작업한 "FORBIDDEN" status를 응답받았고 실제로도 status로 403을 응답받은 것을 볼 수 있다.


DB에서 직접 ROLE을 "USER"로 UPDATE 하고 다시 작성해보자.

세션에는 이미 "GUEST"인 정보로 저장되어있으니 로그아웃한 후 다시 로그인 하여 글 등록을 진행하면 글 등록이 가능해집니다. 😁








제가 준비한 내용은 여기까지 입니다. 처음 포스팅하고 많이 부족한 글이지만 읽어주셔서 감사합니다!! 혹시 틀린 내용이나 코드가 있을 경우 언제든지 말씀해주시면 감사하겠습니다.

profile
daily study

1개의 댓글

comment-user-thumbnail
2024년 1월 24일

감사합니다~

답글 달기