들어가며
인증과 인가는 백엔드 개발자라면 당연히 알아야 하는 개념 중 하나이다. 인증가 인가에 대한 개념 자체는 크게 복잡한 내용을 다루고 있지 않지만, 문제는 쉽지 않은 구현 과정이다. HTTP 통신이 지닌 한계점을 웹 브라우저의 저장소 ( 로컬 스토리지, 쿠키 등 )과 어플리케이션 레벨에서 관리해야 하는데, 이런 식으로 인증/인가를 구현하는 데 다양한 바리에이션이 존재한다.
이번 포스팅에서는 인증과 인가의 개념과 어떻게 구현할 수 있는 지 레벨까지 다뤄보도록 한다.
Authentication vs. Authorization
인증(Authentication)
인증은 사용자의 신원을 검증하는 과정을 말한다. 우리가 흔히 웹사이트에 로그인하기 위해 아이디와 비밀번호를 입력하는데, 이러한 계정 정보가 일치하는 경우, 서버에서 인증해 주게 된다.
인가(Authorization)
인증된 사용자가 어떠한 자원에 접근할 수 있는 지를 확인하는 절차를 말한다. 인가 과정은 사용자 자체에는 관심이 없고, 어떠한 자원에 접근 권한만 확인한다. 실제로 어플리케이션 레벨에서 생각하면, 인가하는 과정에서는 토큰, 세션 등을 확인하여 이 사용자가 접근 권한이 있는 지만 다루게 된다.
인증 vs 인가
인증과 인가에 익숙하지 않다면 헷갈릴 수 있다. 재미있는 예를 들어 보자. 젊어보이는젊어 보이는 한 사람이 술집에 간 상황이라고 하자! 술집 이름은 공사판이 좋겠다. 젊어 보이는 사람은 술을 사기 위해서는 성인이라는 것을 인증해야 한다. 자신의 신분증을 직원1에게 보여주면 직원1은 젊어 보이는 사람을 기억하여, 그 사람이 술을 시킬 때, 주문을 받고 술을 제공할 것이다. 이때 술을 제공하는 것을 인가라고 보면 좋겠다.
이러한 예시 외에도 많은 상황이 있겠지만, 일반적인 상황에서도 이와 비슷하게 인증이 먼저 이루어지며, 인가가 나중에 이루어지게 된다. 이제 서버 관점에서 생각해보자. 서버로 HTTP 요청을 보내는 상황이다. 요청이 서버에 도달하기 전에 많은 필터를 거치게 되는데, 인증 및 인가 실패로 인한 에러의 발생 포인트를 살펴보면서 차이점을 이해해 보자.
실제로 HTTP 상태 코드를 살펴보면 다음과 같다. HTTP 상태 코드 중 401 Unauthorized와 403 Forbidden이 있는데, 다음의 설명을 보면서 인증과 인가에 대해서 좀 더 이해해 보자.
valid authentication credentials이 부족하면 401 Unauthorized 에러가 발생한다. 즉 인증에 실패한 것이다.
403 에러는 서버가 요청을 이해하였으나, 인가하는 것을 거절했다고 한다. 즉, 인가에 실패한 것이다.
401 Unauthorized ( 인증 실패 )는 client request has not been completed이다. 즉 서버에 요청이 전달되지 않은 것이고, 403 Forbidden ( 인가 실패 )는 the server understands the request but refuses to authorize it이다. 서버에는 도달했지만, 서버 내부 로직에 의해서 특정 리소스에 접근 권한이 없어 서버에서 발생시키는 에러이다.
인증이 실패한 경우에는 서버 자체에 요청이 닿지 못한다. 실제로 spring security 같은 라이브러리를 통해 구현하게 되면 HTTP 요청을 낚아채는 Servlet(HTTP를 다루기 쉽게 만들어주는 자바 라이브러리) 계층에서 요청이 막히고 401 상태 코드가 반환된다. 403 에러 같은 경우는 특정 사용자가 다른 사용자의 resource에 액세스 하려고 할 때, 서버에서 에러를 발생시킨다. 요약하면, 인증 이후 인가가 이루어지는 것이고 위와 같은 차이점이 있다는 것이다.
인증과 인가의 구현
인증과 인가의 개념은 어느 정도 이해가 되었을 것이다. 문제는 인증/인가를 구현하는 과정이 굉장히 복잡하다는 것이다. 그 이유의 시작점은 HTTP는 기본적으로 stateless protocol이라는 것이다.
자, 다시 술집으로 돌아오자. 술을 한 병 더 주문했다. 그러자 직원2가 와서 대뜸 "당신은 누군데 어려 보이는 것이 술을 주문하는 거죠?"라고 한다. 맞다. 인증은 직원1에게서 받았기 때문에 직원2는 이를 기억하지 못한다. 이는 HTTP의 특성인 stateless(무상태)와 비슷한 상황인데, 이러한 문제가 서버에서 발생하지 않게끔 어떻게 해야 하는지 알아볼 차례이다.
그렇다면 stateless protocol이 뭔지 알아보자.
A stateless protocol is a communication protocol in which the receiver must not retain session state from previous requests. The sender transfers relevant session state to the receiver in such a way that every request can be understood in isolation, that is without reference to session state from previous requests retained by the receiver. - wikipedia
stateless라는 것은 서버가 클라이언트의 상태를 보존하지 않는 것을 의미한다. 반대로 stateful은 서버가 클라이언트의 상태를 가지고 있는 것을 의미한다. HTTP 통신이 stateless 하기 때문에 다음과 같은 상황이 일어난다.
첫 번째와 두 번째 블록에 따르면 DB에 저장되어 있는 username과 password의 일치를 확인하고 인증되었다. 이제 세 번째 블록인 후드티 한 벌을 주문하는 과정에서 문제가 발생한다. 네 번째 블록의 서버는 request 하는 user에 대한 정보가 없다는 것이다. HTTP 요청은 기본적으로 stateless 하기 때문에 서버가 어떤 사용자(Client)인지 기억할 수 없다.
생각해 보면 말이 안 된다. 서버가 클라이언트의 상태를 보존하지 않는다니 그렇다면 현존하는 서비스들은 대체 어떻게 수많은 회원가입, 로그인 등을 구현할 수 있는 것일까? 바로 서버에서 인증과 인가 과정을 특수하게 구현하여 서버가 클라이언트를 구분할 수 있게 만드는 방법이다. 이번 포스팅에서는 대표적인 방법인 Session 방식과 Token 방식에 대해서 알아보도록 한다. 이는 쿠키, 로컬 스토리지 등이 있는 웹 사이트를 기준으로 설명하도록 한다.
Cookie
쿠키라는 말을 많이 들어보았을 텐데,쿠키는 웹 브라우저에서 저장할 수 있는 작은 저장 공간을 의미한다. 해당 저장 공간에 username과 passowd를 저장하여 매번 HTTP 요청을 보낼 때, 쿠키에 담긴 내용을 보낼 수 있다. 매번의 HTTP 요청마다 서버는 클라이언트의 상태를 확인할 수 있다.
하지만 Cookie은 손쉽게 탈취할 수 있기 때문에 민감한 정보를 쿠키에 저장하는 것은 매우 위험하다. 이러한 방식은 일반적으로 쓰이지 않는다.
첫 번째 방법, Session 방식
쿠키에 민감한 정보를 담으면 안 된다. 따라서 쿠키에는 탈취되더라도 알아볼 수 없는 session ID를 저장하고, server 쪽에서는 세션 스토어를 두어 session ID를 key 값으로 하여 민감 정보를 value로 저장해 둔다. 쿠키의 session ID와 함께 HTTP 요청을 하게 되면, 서버는 해당 Session ID를 통해 세션 스토어에서 정보를 조회하여 사용자의 신원을 확인 후, 인가해주게 된다.
이러한 세션 방식의 장단점은 다음과 같다.
장점 :
- stateful 하게 서버에서 클라이언트의 정보를 저장하고 있기 때문에, 유저를 관리할 수 있다. ex) 강제 로그아웃, 계정 공유자의 수 확인하기 ( 넷플릭스 )
단점 :
- 세션을 저장할 DB를 따로 구비해야 한다.
- 세션 DB를 어디에 두냐(서버 안, 서버 밖)에 따라 아키텍처의 복잡도가 달라진다.
두 번째 방법, Token 방식
인증/인가에 쓰이는 대표적인 토큰인 JWT를 기준으로 설명한다. JWT의 구조는 위와 같다. 여러 색으로 표현된 맨 위의 한 줄이 JWT의 전체이고, .(점)을 기준으로 Header, Payload, Signature가 구분되어 있는 토큰이다.
Header, Payload는 복호화가 가능하기 때문에 민감한 데이터를 담고 있으면 안 된다. 유저를 식별할 수 있을 정도의 최소한의 정보만 페이로드에 담고 있어야 하고, JWT가 유효한지 확인하는 방법은 signature이다. 시그니처 부분은 base64로 인코딩 된 헤더, 페이로드, 그리고 시크릿 키가 합쳐져서 암호화된 형태로 쓰여있기 때문에, 시크릿 키가 없다면 검증할 수 없다.
이러한 JWT를 서버에 보내면 서버에 가지고 있는 시크릿 키를 통해서 시그니처 부분을 대조하여 JWT의 유효성을 검증하며, 페이로드에 담긴 최소한의 정보를 통해서 유저에게 인가해줄 수 있게 된다.
이러한 JWT 방법의 장단점은 다음과 같은 방법이 있다.
장점 :
- stateless 하기 때문에, 서버에서 클라이언트의 정보를 관리하지 않아 리소스가 덜 들어간다.
단점 :
- 인가에 관련된 토큰을 서버에서 관리하는 게 아니라, 클라이언트에서 관리하기 때문에 탈취당했을 때의 위험성이 높다.
- stateful 하지 않기 때문에, 유저의 관리가 힘들다. ex) 강제 로그아웃 시키기
마치며
이러한 내용은 인증과 인가에 대해서 기본적인 방법이며, 실제 구현에는 굉장히 많은 내용이 더 들어간다. JWT를 refresh token과 secret token를 두어서 statelss & stateful 하게 관리하여 세션의 장점을 흡수한 토큰 방식, 또한 세션을 보다 복잡하게 관리하여 넷플릭스처럼 특정 계정을 몇 명이 사용하고 있는지 알 수 있게 하는 등 실제로 구현 과정에서는 다양한 방법이 존재한다.
참조
- [Auth0] What is Authentication vs Authorization : https://auth0.com/intro-to-iam/authentication-vs-authorization
- [코딩애플] JWT 대충 쓰면 님들 코딩 인생 끝남 : https://youtu.be/XXseiON9CV0?si=gCyGftzQYVnTaHsl
- [지마켓] 인증/인가는 어디에 어떻게 구현해야 할까 : https://dev.gmarket.com/45#recentComments
- [Mozilla] HTTP Response Status Code : https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
'Developer' 카테고리의 다른 글
git, github에 대해서 (0) | 2024.01.21 |
---|---|
vi 명령어 및 설정 (1) | 2023.12.11 |
Webhook에 대해서 (1) | 2023.10.01 |