JWT를 활용한 인증인가 과정

정진원·2022년 10월 2일
1

MediGo

목록 보기
2/2

MediGo 프로젝트를 진행하며 JWT로 인증인가를 구현하며 고민했던 것들을 정리해본다.
우선 각각의 특징과 인증방식에 대해 알아보자.

1. 각 인증방식의 특징

  • 각각의 브라우저에서 각각의 host별로 사용자의 컴퓨터에 저장된 key-value형식의 데이터 묶음이다.
MockHttpServletResponse:
	Status = 200
	Headers = [Set-Cookie:"userName=jin","password=123"]
  • 속도는 세션보다 빠르지만, 네트워크 전송 과정에서 탈취될 수 있기 때문에 보안적으로 좋지 않다.
  • 사용자가 따로 요청하지 않아도 브라우저가 Request시에 Request Header에 넣어서 자동으로 서버에 전송한다.
MockHttpServletRequest:
	HTTP Method = GET
	Request URI = /user/my/edit
	Headers = [Cookie:"userName=jin","password=123"]
  • 이런 이유 때문에 필요하지 않는 정보도 계속 요청하게 된다. 따라서 데이터의 낭비가 발생하고, 탈취의 위험이 증가한다.
  • 4kb 용량 제한이 있어 많은 정보를 담을 수 없다.

Session

  • Session은 사용자가 웹 브라우저를 통해 웹서버에 접속한 시점으로부터 웹 브라우저를 종료하는 시점까지, 같은 사용자로부터 오는 일련의 요청을 하나의 상태로 보고, 그 상태를 일정하게 유지하는 기술이다.

  • 사용자 정보 파일을 브라우저에 저장하는 Cookie와 달리 Session은 서버 측에서 관리한다. 따라서 속도는 비교적으로 Cookie보다 느리지만, 보안적으로 더 좋다.

Session/Cookie 기반 인증

  • Session/Cookie 기반 인증사용자의 인증 정보가 서버의 세션 저장소에 저장되는 방식이다.
  1. 사용자가 로그인을 하면, 해당 인증 정보를 서버의 Session 저장소에 저장하고, 사용자에게는 저장된 세션 정보의 식별자인 Session ID를 발급한다. 발급된 Session ID는 브라우저에 Cookie 형태로 저장되지만, 실제 인증 정보는 서버에 저장되어 있다.

  2. 브라우저는 인증 절차를 마친 이후의 요청마다 HTTP Cookie 헤더에 Session ID 를 함께 서버로 전송한다.

  3. 서버는 요청을 전달받고, Session ID에 해당하는 Session 정보가 Session 저장소에 존재한다면 해당 사용자를 인증된 사용자로 판단한다.

Session/Cookie 기반 인증의 장단점

장점

  1. 세션의 경우 Cookie 헤더에 Session ID만 실어 보내면 되므로 트래픽을 적게 사용한다.
  2. 모든 인증 정보를 서버에서 관리하기 때문에 보안 측면에서 조금 더 유리합니다. Session ID가 해커에게 탈취된다고 하더라도, 서버측에서 해당 Session을 무효 처리하면 된다.

단점

  1. 확장성에 취약하다.
    일반적으로 웹 어플리케이션의 서버 확장 방식은 여러대의 서버가 요청을 처리하게 된다. 하지만, Scale up 이 아닌 Scale out 방식으로 서버증설을 하면, 각 서버마다 각각의 IP 를 가지게 되고, 도메인 서버가 이를 랜덤으로 연결하는데 해당 Session이 있는 서버에서만 인증 검사가 가능하기 때문에 여러 제약조건이 있다. 이는 결국 균등한 서버 부하 분배 취지와도 맞지 않고 설정 과정 자체가 복잡해진다. 또한, 별도의 작업을 해주지 않는다면, Session 불일치 문제가 발생한다.
  2. Session증가에 따른 메모리 및 DB 과부하가 발생한다.
    유저의 인증 정보는 서버에 저장하는데 유저의 수가 증가하게 되면 서버의 램이 과부화가 된다. 이를 피하기 위해 DB에 저장한다 해도 DB 성능을 저하시킬수 있다.
  3. CORS(Cross-Origin Resource Sharing)
    웹 어플리케이션에서 Session을 관리할 때 자주 사용되는 Cookie는 단일 도메인 및 서브 도메인에서만 작동하도록 설계되어 있다. 따라서 Cookie를 여러 도메인에서 관리하는 것은 번거롭다.
  4. Stateful하다.
    REST의 제약 조건인 Stateless를 지키지 못한다.
    여기서 Stateless란 각 요청 간 클라이언트의 콘텍스트가 서버에 저장되어서는 안되고, 서버는 작업을 위한 클라이언트의 상태 정보를 따로 저장하고 관리하지 않는다는 특징이다.

JWT(JSON Web Token)

  • Json 포맷을 이용하여 사용자에 대한 속성을 저장하는 Claim 기반의 Web Token이다. JWT는 토큰 자체를 정보로 사용하는 Self-Contained 방식으로 정보를 안전하게 전달한다. 주로 회원 인증이나 정보 전달에 사용된다.

구조

  • Header, Payload, Signature로 이루어진다.
  1. Header
    • typalg 두 가지 정보로 구성된다. typ엔 Token의 타입이, alg엔 HMAC SHA256 또는 RSA 와 같이 사용중인 서명(Signature) 알고리즘이 담긴다.
  2. Payload
    • Json 형태로 다수의 정보를 넣을 수 있다.
    • 토큰에서 사용할 정보의 조각들인 클레임(Claim)이 담겨 있다.
    • 클레임은 총 3가지이다.
    1. 등록된 (registered) 클레임
      • 서비스에서 필요한 정보들이 아닌, 토큰에 대한 정보들을 담는 클레임이다.
    2. 공개 클레임(Public Claim)
      • 공개용 정보를 위해 사용된다. 충돌 방지를 위해 URI 포맷을 이용한다.
    3. 비공개 (private) 클레임
      • 사용자 정의 클레임으로, 서버와 클라이언트 사이에 임의로 지정한 정보를 저장한다.
  3. Signature
    • Token을 인코딩하거나 유효성 검증을 할 때 사용하는 고유한 암호화 코드이다.
    • Header와 Payload의 값을 각각 BASE64Url로 인코딩하고, 인코딩한 값을 비밀 키를 이용해 Header에서 정의한 알고리즘으로 해싱을 하고, 이 값을 다시 BASE64Url로 인코딩하여 생성한다.

Token 기반 인증

  • Token 기반 인증인증 정보를 클라이언트가 직접 들고 있는 방식이다. 이때 인증 정보가 Token의 형태로 브라우저의 Local Storage나 Cookie에 저장된다.

  1. Token 기반 인증에서는 사용자가 가지고 있는 Token을 HTTP 의 Authorization 헤더에 실어 보낸다.
  2. 헤더를 수신한 서버는 Token이 위변조 되었거나, 만료 시각이 지나지 않은지 확인한 이후 Token에 담겨있는 사용자 인증 정보를 확인해 사용자를 인가한다.

Token 기반 인증의 장단점

장점

  1. Stateless하고 높은 확장성(Scalability)을 가질 수 있다.
    토큰 기반 인증 방식의 경우 서버가 직접 인증 방식을 저장하지 않고, 클라이언트가 저장하는 방식을 취하기 때문에 이런 Session 불일치 문제를 겪지 않아서 HTTP의 비상태성을 그대로 활용할 수 있고, 따라서 높은 확장성을 가질 수 있다.
  2. 여러 플랫폼 및 도메인에서의 활용 가능성이 높다.
    토큰만 유효하다면 어떤 디바이스, 도메인이던 간에 요청이 정상적으로 처리된다.
  3. 확장성(Extensibility)
    로그인 정보가 사용되는 분야를 확장한다. 예시로 OAuth에 토큰에 선택적인 권한만 부여하여 발급할 수 있다.
  4. 보안성
    쿠키의 탈취및 위조 문제로 인한 취약점이 사라진다.
    하지만 토큰 또한 보안적 문제가 완전히 해결된 것은 아니다.
  5. 웹 표준 기반
    토큰 기반 인증 시스템의 구현체인 JWT는 웹 표준 RFC 7519 에 등록이 되어있다.

단점

  1. Token은 담겨있는 정보가 Session ID에 비해 비대하므로 세션 방식보다 훨씬 더 많은 네트워크 트래픽을 사용한다.

  2. Token은은 서버가 트래킹하지 않고, 클라이언트가 모든 인증정보를 가지고 있다. 따라서 Token이 한번 탈취되면 해당 Token은이 만료되기 전까지는 피해를 입을 수 밖에 없다.

MediGo 서비스는 기본적으로 REST 법칙을 준수한 API를 활용한다. 앞서 말했듯이 Cookie/Session 기반 인증방식을 사용하게 된다면 REST의 제약조건인 Stateless를 지킬 수 없기 때문에 JWT를 이용한 Token 기반 인증방식을 선택했다.

3. JWT의 보안문제를 해결하기 위한 고민

앞서 말했듯이 토큰은 한 번 발급되면 유효기간이 만료될 때 까지 계속 사용이 가능하기 때문에 탈취됐을때 대처가 쉽지 않다. 이를 위해 고민한 방안들은 다음과 같다.
1. 짧은 만료기간 설정

  • 토큰이 탈취되더라도 빠르게 만료되기 때문에 피해를 최소화할 수 있다. 그러나, 사용자가 자주 로그인해야 하는 불편함이 수반된다.
  1. Refresh Token
  • 클라이언트가 로그인 요청을 보내면 서버는 Access Token 및 그보다 긴 만료 기간을 가진 Refresh Token을 발급하는 전략이다.
  • 클라이언트는 Access Token이 만료되었을 때 Refresh Token을 사용하여 Access Token의 재발급을 요청한다.
  • 서버는 DB에 저장된 Refresh Token과 비교하여 유효한 경우 새로운 Access Token을 발급하고, 만료된 경우 사용자에게 로그인을 요구한다.
  • 해당 전략을 사용하면 Access Token의 만료 기한을 짧게 설정할 수 있으며, 사용자가 자주 로그인할 필요가 없다.
  • 그러나, 검증을 위해 서버는 Refresh Token을 별도의 저장소에 저장해야 한다. 이는 추가적인 I/O 작업이 발생함을 의미하기 때문에 JWT의 장점(I/O 작업이 필요 없는 빠른 인증 처리)을 완벽하게 활용할 수 없다. 또한, 탈취 방지를 위해 Refresh Token을 보안이 유지되는 공간에 저장해야 한다. 따라서, 해당 토큰의 저장위치에 대한 고민이 필요하다.

말했듯이, 만료기간을 짧게 설정하면 사용자가 자주 로그인해야 하는 불편함이 수반된다. 마이데이터를 활용한 서비스인데 잦은 로그인을 요구한다면 사용자 경험 측면에서 좋지 않을 것이라 생각했다. 따라서, Access Token과 Refresh Token을 활용해 보안을 강화하기로 했다.

4. 각 Token을 어디에 저장할것인가?

Access Token

Local Storage

  • AccessToken은 5분에서 30분으로 탈취의 위험을 고려하여 매우 짧게 설정한다. 그럼에도 불구하고 탈취에 위험이 있으므로 저장위치에 대한 고려가 필요하다.
  • 장점
    • 만료날짜 없이 데이터를 저장할 수 있어 브라우저를 닫거나 다시 열어도 데이터를 사용할 수 있다.
    • 쿠키처럼 자동으로 request에 담기지 않기 때문에 공격자가 정상적인 사용자인 척 request를 보내기가 어렵다.
  • 단점
    • JS 를 통해 localStorage 에 접근하면 바로 토큰에 접근할 수 있다.
    • 공격자가 악의적인 js 코드를 피해자 웹 브라우저에서 실행시키는 XSS 해킹 공격을 통해 Token 탈취가 가능하다. 즉, 공격자의 js 코드 한 줄로 Token 탈취가 가능하다.
  • Token 값을 서버에서 Cookie에 담아 전달하면 클라이언트는 Request시 해당 Cookie를 항상 서버에 전달한다. 이 Cookie에 있는 Token값을 통해 인증 인가를 처리를 한다.

  • 장점

    • Cookie 설정 시 httpOnly 값을 활성해 준다면 js로 Token 값에 접근하는 것이 불가능해져 XSS 해킹에 Local Storage보다 더 안전하다 할 수 있다.
      하지만 XSS 공격으로부터 완전히 안전한 것은 아니다.
      httpOnly 옵션으로 쿠키의 내용을 볼 수 없다 해도 js로 request를 보낼 수 있으므로 자동으로 request에 실리는 쿠키의 특성 상 사용자의 컴퓨터에서 요청을 위조할 수 있기 때문이다. 즉, XSS가 뚫린다면 httpOnly cookie도 안전하진 않다. (관련 사이트)
    • Secure 옵션을 통해 쿠키가 HTTPS로만 전송되게 하여 보안 수준을 높일 수 있다.
  • 단점

    1. 쿠키는 한정된 도메인에서만 사용된다.

    2. CSRF 공격에 취약하다.
      CSRF 공격이란 사용자가 자신의 의지와 무관하게 공격자가 의도한 행위를 특정 웹사이트에 요청하게 하는 공격을 말한다. 즉, 자신도 모르게 서버를 공격하는 경우이다.
      Cookie에선 자동으로 request에 담겨서 보내지기 때문에 공격자가 request url만 안다면 사용자가 관련 link를 클릭하도록 유도하여 request를 위조하기 쉽다.

      • 해결방법 : SameSite 설정을 활용한다.
        2020.2.4 구글 크롬(Google Chrome) 80버전으로 업데이트되면서 CSRF 공격을 막기위해 크롬에 Cookie 정책에서 SameSite 속성의 기본 값이 "None"에서 "Lax"로 보안등급을 상향 조절됐다."Lax"등급에선 기본적으로 서로 다른 도메인에서는 Cookie 전송이 불가능하지만 HTTP get method / a href / link href와 같은 특수한 경우엔 쿠키를 전송한다.

어짜피 Cookie의 httpOnly 옵션도 XSS 공격을 완벽히 막을 수 없다. 보안적인 관점에서 이에 대한 Cookie의 장점이 매력적이게 보이지 않았다. 또한, 크롬의 Cookie SameSite 정책 변경으로 다른 도메인으로 Cookie를 전송해야하는 상황에 추가적인 설정이 필요했다. 프로젝트 마감 기간이 얼마 남지 않은 상황에서 이 설정을 적용하는게 얼마나 걸릴지 예측이 가지 않았다. 이러한 이유로 Local Storage에 Access Token을 저장하기로 했다.

Refresh Token

refreshToken 은 보통 매우 긴 시간을 설정하기 때문에 보안적으로 매우 중요하다. 따라서 서버에서 관리하여야 한다.

DataBase

  • 일반적으로 사용하는 DB에 Refresh Token을 저장하는 것을 고려할 수 있다. 하지만, 백엔드 팀원이 Spring Context 밖에 있는 Web Context에 등록된 filter에서 영속성 관리를 하는 repository에 접근하여 엔티티를 다루는 것은 좋은 설계 같지 않다고 의견을 냈다. (Web Security에서 빈등록은 하지만, 기본적으로 해당 로직은 비즈니스 로직에서만 접근하고 싶다고 했다.)
  • Interceptor 로 인증인가 처리를 하기엔 이미 filter 에서 인증을 처리하기 때문에 모든 관련 로직들을 filter 에서 다뤄야 한가고 했다. 따라서, 다른 저장소를 찾았다.

Redis

  • Redis는 주기억장치에 데이터를 저장하므로 DB 보다 빠르고 성능이 좋다. 또한, 전원이 꺼지면 데이터가 날아간다. 그래서, 자주쓰고 날아가도 괜찮은 정보를 Redis에 저장한다면 성능적으로 좋다고 생각했다.

    위의 이유때문에 Redis 를 Refresh Token을 저장하기에 제일 적합한 DB 라고 결정했다.

추가적인 문제 발생

  • 앞서 말한대로 JWT를 활용한 인증인가를 Access Token, Refresh Token을 사용해 구현했다. Access Token은 Local Storage에, Refresh Token은 Redis에 저장했다.
  • 하지만, SEO에 강점이 있는 Next js의 특징을 살리고자 페이지를 SSG(Static-Site-Generation)를 활용해 Pre-Render하는 과정에서 문제가 발생했다. 글이 길어지니 관련 내용은 SSG와 관련된 다음 포스팅에서 다루겠다.

참고 사이트

https://tecoble.techcourse.co.kr/post/2021-05-22-cookie-session-jwt/
https://hackernoon.com/using-session-cookies-vs-jwt-for-authentication-sd2v3vci
https://mangkyu.tistory.com/56
https://park-algorithm.tistory.com/entry/%EC%9D%B8%EC%A6%9D%EA%B3%BC-%EC%9D%B8%EA%B0%80%EC%BF%A0%ED%82%A4-%EC%84%B8%EC%85%98-JWT-OAUTH-20
https://developers-kr.googleblog.com/2020/12/schemeful-same-site.html
https://velog.io/@0307kwon/JWT%EB%8A%94-%EC%96%B4%EB%94%94%EC%97%90-%EC%A0%80%EC%9E%A5%ED%95%B4%EC%95%BC%ED%95%A0%EA%B9%8C-localStorage-vs-cookie
https://program-developer.tistory.com/99

profile
깊이 있는 학습, 클린 코드, 의사소통

0개의 댓글