다시 한번 살펴보는 auth 인증과정 (feat. cookie, token)

okkkkkky·2024년 4월 5일
0

특정 사이트에 로그인을 하고 난 후, http의 stateless한 특성 때문에 로그인을 유지하기 위해 지속적으로 요청하는 클라이언트가 요청할 수 있는 권한이 있는지 없는지 검증하는 과정을 거쳐야 합니다.

auth 인증방식

token (예. JSON Web Token)

auth 인증방식 중 아주 간단하게 사용할 수 있는 토큰 방식이 있는데, 그중 대표적인 예시가 JSON Web Token(jwt)입니다. 클라이언트와 서버에서 통신할 때, 사용자를 인증하고 권한을 부여하기 위한 토큰이며, 아래와 같은 방식으로 구성되어 있습니다.

jwt 구성

jwt의 경우에는 jwt.io 사이트를 통해 간단하게 토큰의 header, payload를 복호화 할 수 있습니다. 다만, 마지막 signature는 서버만의 암호화된 키가 사용되기 때문에 쉽게 복호화 할 수 없습니다. 이 signature를 통해 서버는 이 토큰을 가지고 있는 유저가 실제 유저인지 아닌지 확인할 수 있게 됩니다.

Jwt 인증과정

session id

한 유저가 서버에 접속해있는 상태를 한 단위로 생각했을 때, 이 단위를 세션이라고 합니다. 이 세션에 부여된 id는 서버의 DB상에 저장되어 이 유저가 실제 유저인지 아닌지 DB에 저장된 id값과 비교하여 검증을 하게 됩니다. 아래의 이미지처럼요 !

session id 인증과정

token vs session ?

그렇다면, token이 낫나요 session이 낫나요 ? 사실 답은 없습니다. 대표적인 차이점은 token은 클라이언트 사이드에 저장되고, session은 서버 사이드에 저장됩니다. 하지만 내부의 상황에 따라, 그리고 아래의 상황에 따라 다르게 적용해볼 수 있겠습니다.

token

  • 장점)
    • 인증과 관련된 서버 혹은 DB에 의존적이지 않다.
    • 접속한 유저가 갑자기 늘어나도 서버에 부하가 가지 않는다.
  • 단점)
    • 탈취의 가능성이 있다. (XSS - 잘못된 자바스크립트 실행을 통한 쿠키 탈취)

session

  • 장점)
    • id의 값으로 인증되기 때문에 탈취되더라도 복호화될 내용이 없다.
    • session id가 탈취되었을 때, 바로 무효화시킬 수 있음
  • 단점)
    • 접속한 유저가 급격히 많아지면 서버에 부담이 될 수 있고, 별도로 관리하는 서버가 필요하다. (CORS 이슈도 발생 가능)
    • 쿠키에 저장된 session id값을 사용하여 잘못된 요청을 보낼 수 있다. (하이재킹 - 세션 가로채기)

번외) access token & refresh token

token 인증방식에서 access token, refresh token 총 2가지의 token을 사용하는 것이 일반적인데요, 왜 access token과 refresh token 2가지를 사용하게 되었을까요 ?

token의 경우 탈취의 가능성이 있기 때문에, 유효기간을 설정하여 관리해주어야 합니다. 하지만 짧은 유효기간을 설정하는 경우 유저는 기간이 끝날때마다 재로그인을 해야 하는 번거로움이 생길 수 있겠죠 ! 이를 위해 api 호출 전용으로 만든 access token과 이 access token이 만료되었을 때, 새로운 access token을 받을 수 있게 할 수 있는 refresh token을 같이 받는 것입니다. 아래의 이미지는 refresh token을 받는 과정을 소개합니다.

access token의 유효기간이 끝난 경우

포인트는 아래의 단계들입니다.

6. access token이 만료되었을 때
8. 401에러를 뱉어주는 서버
9, 10. 그리고 이 에러를 감지하고 access token의 재발급 요청을 하는 클라이언트
11. refresh token이 유효한지 확인 후 access token을 다시 재발급해주는 서버 

간혹 refresh token 조차 탈취되는 경우가 있기에, (위의 이미지 상 12번 단계인) 신규 access token을 전달할 때, refresh token도 신규로 발급하여 같이 주는 경우도 있을 수 있습니다.
유효기간이 아주 긴 access token 한 개만으로는 UX적으로 좋을 수 있겠으나 탈취당했을 때 기간이 너무나 길게 되어버리면 탈취 이후의 피해는 오히려 더 오래 지속될 수 있기 때문에 기간을 짧게한 access token + refresh token의 합이 더 보안적으로 대응을 잘 할 수 있을 것입니다.

인증정보의 전달 방법

위에서는 인증방식에 대해서 이야기 해보았다면, 이제 이 인증 정보를 어떻게 줄것인지 고민해보아야겠습니다. 고려해볼 수 있는 방법은 크게 아래의 2가지 방법이 있습니다.

위의 이미지들에서도 나와있듯, 서버는 클라이언트의 요청에 대한 응답 헤더에 set-cookie라는 항목으로 인증과 관련된 값들을 실어보내줍니다. 아래의 이미지에서는 12345라는 session-id를 Set-cookie로 전달해주고 있네요 ! 이 정보를 가지고 클라이언트(브라우저)는 request header에 cookie라는 이름으로 서버에서 전달해준 12345 session id를 그대로 전달해줍니다.

HTTP 통신에서의 cookie 교환방식

왜 서버에서 set-cookie로 값을 설정해주나요 ?

인증과 관련된 정보들, 특히나 유저의 정보들과 관련된 것들을 가지고 있는 서버에서 set-cookie로 response Header에 전달을 해주어야 인증에 대한 관리가 용이합니다. 사용자의 세션을 유지하거나, 보안의 이슈들이 그 관리 대상이 될 수 있겠습니다. 그리고 무엇보다 서버에서 set-cookie를 하게 되면 브라우저는 그 response의 헤더로 들어온 cookie값을 브라우저 내부에 저장만 하면 되기 때문에 브라우저와 서버 모두에게 이득이 되겠죠 !

하지만 이렇게 set-cookie로 서버가 안전하게 전달해준다 하여도, 쿠키는 언제든 탈취의 대상이 될 수 있으며, 공격 대상이 되기 쉽습니다. 이를 예방하기 위해서는 어떤 속성들이 필요할까요 ?

cookie에 대한 탈취를 방지하기 위한 간단하지만 중요한 옵션들

완전한 탈취방지는 쉽지 않지만, 아래의 속성들을 설정해준다면 쿠키의 탈취를 조금 더 어렵게 하여 보안성을 높일 수 있습니다.

Secure

Secure 속성을 명시해주는 경우, HTTPS 프로토콜을 사용하여 서버에 요청을 하는 경우에만 쿠키가 전송될 수 있습니다. (localhost의 경우는 제외됩니다) 하지만, 이 속성만 설정한다고 하여 쿠키를 안전하게 지킬 수는 없습니다.

HttpOnly

HttpOnly 속성이 명시된 쿠키는 javascript가 document.cookie를 통해 쿠키에 접근하지 못하도록 막습니다. 이 속성은 크로스 사이트 스크립팅(XSS) 공격, 즉 script를 이용해 이용자의 정보를 탈취하는 것에 "어느 정도" 대응할 수 있게 합니다.

SameSite

이 속성은 쿠키가 전달되는 사이트의 범위를 제한할 수 있습니다. 동일 사이트인지 아닌지 구분하는 기준은 바로 도메인입니다. 사이트란 도메인 서픽스와 바로 앞 도메인 부분의 조합입니다.

www.web.dev = web.dev 사이트의 일부
www.web.dev에 있는 사용자가 static.web.dev의 이미지를 요청 = 동일 사이트 요청

공개 접미사의 경우에는 동일한 사이트로 판단할 것인지 아닌지를 정의합니다. 예를 들면, 아래의 두 사이트는 별도의 사이트로 집계됩니다.

** github.io (.com과 동일한 접미사의 기능을 합니다)
my-project.github.io에 있는 사용자가 your-project.github.io에 있는 이미지를 요청 = 크로스 사이트 요청 

SameSite의 경우, none, lax, strict 3가지의 값으로 설정될 수 있는데, 최근 크롬 브라우저에서는 SameSite의 기본설정값이 none에서 lax로 변경된 이슈도 있었습니다. 세 가지 속성은 아래와 같은 범위를 가지고 있습니다.

  • None: 사이트가 다르건 같건, 쿠키를 모두 전달합니다. 보통 광고성 사이트, 다른 사이트에서 삽입된 컨텐츠 등을 제공하는 경우 None을 사용하여 의도를 명확하게 해야 합니다.
  • Lax: 크로스 사이트 요청에서도 쿠키의 전달을 허용합니다. 다만 위에서 언급한 크로스 사이트임이 맞더라도, 아래의 두 조건은 만족해야 합니다.
    • 최상위 링크값이 변경되는 경우 (말그대로 브라우저의 url이 변경되는 경우입니다. iframe을 통해 타 사이트로 접근하는 경우, 브라우저의 url은 변경되지 않기 때문에, 쿠키는 전달되지 않습니다)
    • 안전한 HTTP method를 사용하는 경우 (GET (O) / POST, DELETE (X))
  • Strict: 동일 사이트 요청에서만 쿠키의 전달을 허용합니다. 보통 사용자 작업과 관련된 쿠키가 이 값으로 설정될 수 있습니다.

Domain & Path

Domain과 Path 속성은 어떤 url로 쿠키를 보내야 할지, 쿠키의 스코프를 지정합니다.
Domain은 쿠키가 전송되게 될 호스트를 명시하여 설정해줍니다. 설정하는 경우 앞의 서브도메인에 어떤 값이 오든 쿠키를 전송한다는 뜻이 됩니다.

Domain=DomainText, Domain=o2.org
- www.o2.org => Domain cookie가 있음
- test.o2.org => Domain cookie가 있음

Path의 경우, 클라이언트에서 Cookie 헤더를 전송하기 위해 요청되는 url 내에 반드시 존재해야 하는 url을 명시하는 속성입니다.

Path=PathTest, Path=/cookie
- localhost:3000/ => Path cookie가 없음
- localhost:3000/cookie => Path cookie가 있음

위에서 언급한 쿠키 속성들은 완벽하게 쿠키의 탈취를 예방할 수는 없습니다. 다만, 쿠키가 탈취당할 수 있는 가능성을 줄여주는 중요한 기능이기 때문에 되도록 신경써주어 설정해야 할 필요가 있습니다 !

payload

조금은 색다른 방법일 수 있습니다. 서버에서 set-cookie를 하지 않고, 클라이언트에서 set-cookie를 할 수 있게끔, 특정 api에 대해서 호출할 때만 응답값, 즉 payload로 token정보를 주는 방식입니다. 예를 들어, login과 관련된 api를 호출하는 경우 아래와 같이 정보를 줄 수 있습니다.

{
  permission: ["admin", "commerce", ...],
  token: {
    accessToken: "XXXXX",
    refreshToken: "YYYYY",
    type: "Bearer",
    maxAge: "..."
  }
}

서버에서 내려주는 경우에는 response Header에 담아주는 정보를 클라이언트에서는 사용할 수 없기 때문에 만약 서버에서 DB 조회를 최소화 시키기 위해, 그렇게 중요하지 않은 정보를 payload에 실어보내는 경우, 클라이언트에서 접근하여 사용할 수 있게 됩니다. 또한 쿠키를 설정하거나, 삭제하는 경우에도 서버가 아닌 클라이언트에서만 관리를 할 수 있기 때문에 조금 더 명확하게 책임분리를 할 수 있을 것 같습니다.
하지만 클라이언트에서 쿠키를 저장하는 것 자체가 부담일 수 있고, 무엇보다 탈취 노출의 위험이 더더욱 커질 수 밖에 없겠네요.

그럼, set-cookie는 어디서 해주어야 하나요 ? (feat. Next.js)

이렇게 서버에서 token값을 넘겨주는 경우, 클라이언트는 어디에서 set-cookie를 해줄 수 있을까요 ? Next.js 프레임워크에서 언급을 하자면, 바로 Express server에서 관리해줄 수 있습니다.
payload로 token 정보들을 넘겨주는 경우, 특정 api에 대해서만 응답값으로 넘겨주는데요(예. login api, sign up complete api), 이 경우 Next.js 내부에서 구동되는 Express server에서 특정 api에 대해 별도의 set-cookie 과정을 거치는 로직을 구성할 수 있습니다.
혹은, middleware를 구성하여, api request와 response에 대해 특정 req.url에 대해 분기처리를 하여 token값들을 serialize해줄 수도 있을 것 같습니다.

참고
https://sherryhsu.medium.com/session-vs-token-based-authentication-11a6c5ac45e4
https://www.loginradius.com/blog/engineering/guest-post/jwt-vs-sessions/
https://80000coding.oopy.io/1f213f10-185c-4b4e-8372-119402fecdd0
https://developer.mozilla.org/ko/docs/Web/HTTP/Cookies
https://seob.dev/posts/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EC%BF%A0%ED%82%A4%EC%99%80-SameSite-%EC%86%8D%EC%84%B1
https://web.dev/articles/samesite-cookies-explained?hl=ko
https://opentutorials.org/course/3387/21745
https://eottabom.github.io/post/refresh-token/

0개의 댓글