[Back-End] 인증/인가와 쿠키/세션/토큰(JWT)

    Intro


    프로젝트를 진행하며 로그인 부분이 제일 쉬울거라 생각했는데, 가장 많은 시간이 걸리고 어렵고 아직도 완벽히 이해를 못한 부분이 로그인 처리 과정이다.

    단순 유저 입장에서 보면, "그냥 Oauth로 로그인을 하거나, 아이디/PW 쳐서 로그인하고 서비스 이용하면 되는데 뭐가어렵지?" 라고 생각할 수 있다. 하지만, 생각해보자. 네이버 메인페이지에서 로그인을 했다. 그리고 네이버 메일로 들어가서 내 메일을 확인한다. 이상한 점이 없다고 생각할 수 있지만. "naver.com" 과 "mail.naver.com" 은 다른 주소이기 때문에 "mail.naver.com에서 로그인을 다시 하지 않고 어떻게 내 정보를 가지고 왔지?" 라고 생각이 들 수 있습니다. 그 부분은, 네이버 서버에서 알아서 처리를 하기 때문에 이러한 정보가 연동되는 것이다. 이러한 로그인 처리의 기본이 되는 인증/인가, 세션/토큰에 대하여 내가 공부한 점을 작성해보려고합니다.

     

    인증과 인가


    인증이란 쉽게 말해서, 내가 누구인지에 대한 판별을 하는 것이다.

    예를 들어, A라는 회사에 영업사원으로 입사를 했다고 치자. 그리고 그 회사에서는 사원들에게 사원증이라는 카드를 하나씩 발급을 해준다. 아침에 출근을 하거나, 점심을 먹고 사무실에 들어갈 때 항상 사원증을 태깅하여 내가 이 회사의 사원이라는 것을 판별받고 난 후에 사무실로 들어갈 수 있다. 만약 지나가던 행인 B씨가 회사에 들어오려고 하면 어떻게 될까? 사원증이 없기 때문에 이 회사의 사원이라는 '인증'을 받을 수 없을 것이다. 이것이 인증이다.

    그렇다면 인가란 인증과 무슨 차이가 있을까?

    위에 적었다시피, 나는 영업사원으로 회사에 속해 있다. 그런데 임원 회의라던지, 다른 부서(예를 들면 마케팅 팀)의 회의에 내가 맘대로 껴도 되는 걸까? 당연히 아니다. 즉 회사에 속해 있다는 인증은 받았지만, 내가 해도 되는 일과 하면 안되는 일, 즉 사원마다 권한의 차이가 존재한다.

    이것이 인가이다.

    이제 다시 웹의 세상으로 돌아와서 이야기를 해보자. 티스토리에 로그인을 하는 것은 '인증' 이다. 그런데 현재, 내가 작성하고 있는 이 블로그에 누구나 글을 작성할 수 있을까? 아니다. 블로그의 주인인 나만 '글을 작성 및 수정하는 권한'을 가지게 되고, 블로그의 주인이 아닌 유저들은 '읽는 권한'만 가지게 된다. 즉 '인가'의 영역이다.

     

    그렇다면, 서버에서는 이 인증과 인가를 어떻게 처리하게 될까??

    보통은, 쿠키/세션/토큰을 이용해서 처리하게 된다.

     

    쿠키 / 세션 / 토큰(JWT)을 이용한 인증 방식


    먼저, 우리는 HTTP 방식을 통해 클라이언트와 서버가 통신을 한다는 것을 알고 있습니다.

    HTTP 방식은 보통 클라이언트가 서버에 요청(Request)을 보내면, 이 요청에 맞게 서버에서는 응답(Response)을 해주게 굅니다.

    또한 각 요청은 독립적이게 되는데, 내가 유저로서 서버에 요청을 보내면 서버는 그에 맞는 응답을 보낸다. 그럼 만약 그 다음 요청에서 내가 유저라는 사실을 알려주지 않고 보내도, 서버는 알아서 "어? 방금 전에 유저가 요청을 보냈으니, 이번에 온 요청도 유저가 보낸거겠지?" 하고 응답을 할까? 아닙니다. 1명만을 위해 만든 서비스가 아니고서야, 비회원도 요청을 보낼 수 있고, 유저 1, 2, 3 등 여러 유저가 각각 요청을 보낼 수 있는 것입니다. 즉, 각 요청은 독립적이게 되는 것입니다. 이것이 HTTP의 특성인 "statelessness(무상태성)"입니다.

    그럼, 여기서 알 수 있는 사실은 내가 유저로서 요청을 보내려면 서버에게는 항상 내가 유저라는 사실도 같이 전달해야한다는 것입니.

    이렇게, 서버에서 인증된 회원을 관리하기 위해서 우리는 쿠키 / 세션 / 토큰을 사용하는 것입니다.

     

    쿠키 인증 방식

    위의 예에서, 우리는 유저임을 서버에 알려주기 위해서 요청에 정보를 담아서 서버에 보내야한다고 했습니다.

    먼저, 로그인을 진행하면 서버는 어떤 유저인지 그 순간에 파악할 수 있겠죠? 그렇다면, 그 이후에 공개가능한 정보(ID/PW 같이 중요한 정보보다는, memberId 같은거..?)를 응답에 담아서 클라이언트에게 보내줍니다.

    클라이언트는, 그 정보를 잘 보관하고 있다가 요청을 보낼때마다 헤더에 쿠키정보를 담아서 같이 보내준다면, 서버 측에서는 쿠키의 값을 가지고 어떤 유저인지 파악할 수 있게 되는 것이죠.

    유저 1, 2, 3이 있다고 가정해 보면, 유저 1이 로그인을 하면 서버는 1이라는 값을 보내주고, 유저 2한테는 2라는 값, 유저 3에게는 3이라는 값을 보내줍니다. 그럼 각 유저는 자신의 값을 저장하고 있다가 서버에 보내주게 되죠. 만약, 요청 헤더의 cookie 에 2라는 값이 있다면? 이건 2라는 유저가 보낸 요청이 되는 것입니다.

    즉, 쿠키 방식은 클라이언트 측에서 정보를 보관하고 있으며 서버에 매 요청마다 해당 정보를 같이 보내주는 방식입니다.

     

    세션 인증 방식

    위의 쿠키 방식에서 조금 더 나아가 생각을 해보겠습니다. 제가 보안적인 부분을 잘 알지는 않지만, 일반적으로 해킹 등의 보안 이슈는 서버보다는 클라이언트 측에서 발생하기 더 쉽습니다. 그렇다는 말은, 쿠키 방식 또한 보안적으로 위험에 처해질 가능성이 높다는 뜻입니다.

    이러한 문제를 해결하기 위해서, 사용자의 정보를 서버 측에 보관하는 세션 방식이 등장하게 되었습니다.

    세션 방식의 흐름은 다음과 같습니다

    사용자 로그인 -> 로그인된 정보를 바탕으로 세션을 생성 후 고유 세션ID 부여 -> 쿠키를 통해 세션 ID를 클라이언트에게 전달 -> 클라이언트는 이후 매 요청마다 세션 ID를 함께 보냄 -> 서버에서 유저 파악 가능

    세션 방식이라고 해서, 쿠키를 사용하지 않는 것은 아닙니다. 하지만, 유저의 데이터를 직접적으로 주고받지 않고 세션ID라는 것을 통해 주고받으며 서버에서 로그인 되어 있는 유저인지 / 아닌지를 판단하는 부분이 다릅니다.

    로그아웃을 예시로 들어보았을 때,

    쿠키 방식 : 로그아웃(클라이언트측에서 쿠키를 삭제) -> 다음 요청부터 쿠키값 없이 보냄 -> 서버에서 비회원으로 간주

    세션 방식 : 로그아웃(서버 측에서 세션ID를 삭제하거나 무효화) -> 하지만 요청에는 세션ID가 포함되서 옴 -> 서버에 일치하는 세션ID가 없으므로 로그아웃 되었거나(삭제) 비정상적인 요청(무효화)으로 판정

    즉, 사용자의 정보를 어느 곳에서 보관하는 것이냐가 쿠키와 세션의 가장 큰 차이이며, 쿠키 인증 방식보다는 세션 인증 방식이 보안적인 측면에서 더 유리하다는 점을 알 수 있습니다.

     

    그렇다면, 세션 인증방식이 만능일까요? 당연하게도 그렇지 않습니다. 적은 수의 요청이 들어올 때는, 세션ID또한 많지 않고 서버에서 모든 요청을 처리하는 데 무리가 없습니다. 하지만 엄청나게 많은 유저와 요청이 들어오게 된다면? 서버에서 보관해야 하는 세션ID가 많아져 메모리 사용량이 높아질 수 있고, 서버에 부하가 걸릴 수 있게 됩니다.

     

    토큰(JWT) 인증 방식

    기존의 세션 인증 방식에서 한걸음 더 나아간 것이 토큰 인증 방식이라고 저는 생각합니다.

    일반적으로 사용하는 jwt 토큰을 이용하여 설명하도록 하겠습니다. 로그인 과정을 설명하기에 앞서, jwt 토큰이 무엇인지 부터 설명하겠습니다.

    JWT 토큰이란 Json Web Token의 약자로 Json객체에 필요한 정보들을 담은 후에 비밀키로 암호화된 토큰입니다.

    AAAAAAAA.BBBBBBBB.CCCCCCCC의 형태로 되어 있으며, 맨 앞의 부분을 Header, 중간을 Payload, 마지막을 Signature라고 부릅니.

    jwt.io 기본 화면

    https://jwt.io/ 페이지에 접속하게 되면, 기본적인 jwt토큰의 형태를 볼 수 있습니. 우측에서 볼 수 있듯이, jwt토큰의 header와 payload부분은 Base64 방식으로 인코딩 되어 있어 디코딩이 가능합니다.

    header 부분에는, 이 토큰이 어떤 알고리즘을 사용하여 암호화되었는지와, 토큰의 타입이 적혀있습니다.

    payload 부분에는 실제로 사용할 정보와 만료 날짜 등이 담기는데, 디코딩이 가능하기 때문에 민감한 정보의 경우 포함하면 안됩니다.

    signature은 우리가 지정한 암호키와 알고리즘을 사용해 생성되므로, 이 signature부분을 통해서 검증된 토큰인지를 판단할 수 있습니다.

     

    그럼 JWT 토큰을 이용한 로그인 흐름을 알아보겠습니다.

    사용자 로그인 -> 로그인 된 정보를 바탕으로 토큰 생성 -> 응답을 통해 토큰을 클라이언트에게 전달 -> 이후 요청마다 클라이언트는 토큰을 Authorization 헤더에 담아서 보냄 -> 서버에서는 비밀 키를 이용해 토큰을 디코딩하여 어떤 유저인지 파악

    하는 방식이 됩니다. 

    그렇다면 보안적으로 쿠키/세션 인증방식에 비해 안전할까요?

    사실 토큰 인증방식 또한 클라이언트 측에 보관되기 때문에 쿠키 인증 방식과 같이 탈취될 가능성이 높습니다.

    그럼 어떻게 보안적인 측면을 유지할 수 있을까요?

    1. 토큰의 만료기간을 짧게 설정한다
    2. 검증할 수 있는 별도의 토큰을 추가한다.

    1번과 같이 토큰의 만료시간을 10분으로 설정하게 된다면, 발급 즉시 토큰을 탈취당하더라도 10분이 지나면 안전해질 수 있습니다. 하지만 그만큼 로그인을 빈번하게 해야 하기 때문에, 실제 사용자가 불편해질 수 있는 여지가 있습니다.

    2번과 같이 별도의 토큰을 추가하는 방법은, 사용자가 로그인을 할 때 응답해주는 AccessToken과 더불어 만료시간이 훨씬 긴 RefreshToken을 같이 주는 것입니다.

    RefreshToken은 AccessToken과 다르게 서버 측에서 보관을 하고 있고, 클라이언트는 AccessToken과 RefreshToken을 같이 보내게 됩니다. AccessToken또한 검증을 진행하고, RefreshToken은 검증과 더불어 서버측에서 보관하고 있는지의 여부까지 같이 검사를 하게 됩니다. 또한 만료시간을 더 길게 주었기 때문에, AccessToken이 만료되었지만 RefreshToken이 유효한 경우에는 새로운 AccessToken을 발급해서 바로 로그인 처리를 할 수 있다는 장점이 있습니다.

    그렇다면 결국 RefreshToken을 서버 측에서 관리한다면 세션과 같은 흐름이지 않나? 싶은 생각이 들 수 있지만 세션 방식과 토큰 방식에는 큰 차이가 있습니다. 바로 상태성입니다. 세션 인증 방식은, 클라이언트의 서버 간의 통신에서 세션 정보를 유지하고 있기 때문에, 유저가 어떠한 상태인지 서버가 알고 있기에 무상태성 이라고 볼 수 없습니다. 하지만, 토큰 인증 방식에서는 RefreshToken이라는 것을 서버에서 보관하고는 있지만, 보안적인 측면에서만 활용되기 때문에 무상태성을 유지하고 있다는 점이 큰 차이입니다.

    Outro


    인증, 인가 및 각 인증 방식에 대해서 공부를 하고 작성해보았습니다. 하지만 세션 방식 또한 장점이 많기 때문에, 프로젝트의 규모 등을 생각했을 때는 번거로운 방식보다는 세션 방식을 선택하기도 한다고 합니다.

    공부를 하며 한 가지 아쉬운 점은, 세션 방식에서 부하가 걸릴 정도가 되는 상황을 직접 경험해보고 싶지만 그러지 못한다는 부분이 아쉬웠습니다. 이러한 부분을 테스트 하거나 경험해볼 수 있는 지 한번 확인해 보고, 경험해볼 수 있다면 꼭 한번 해보고 싶습니다.

    긴 글 읽어주셔서 감사합니다.

    댓글