본문 바로가기
Computer Science/Server

[Server] 세션 기반 인증 VS 토큰 기반 인증

by 우지uz 2024. 6. 13.

https://dzone.com/articles/cookies-vs-tokens-the-definitive-guide

 

세션 기반 인증

 

세션 기반 인증은 오랫동안 사용자 인증을 처리하는 기본 방법으로 사용되어 왔습니다. 

세션 기반 인증은 상태를 유지하는 방식입니다. 이는 인증 기록 또는 세션이 서버와 클라이언트 양쪽에서 유지되어야 함을 의미합니다. 서버는 데이터베이스에 활성 세션을 기록해야 하며, 프론트엔드에서는 세션 식별자가 포함된 쿠키가 생성됩니다. 따라서 이를 쿠키 기반 인증이라고 부릅니다. 전통적인 쿠키(세션) 기반 인증의 흐름을 살펴보겠습니다.

  1. 사용자가 로그인 자격 증명을 입력합니다.
  2. 서버가 자격 증명이 올바른지 확인하고 세션을 생성하여 이를 데이터베이스에 저장합니다.
  3. 세션 ID가 포함된 쿠키가 사용자의 브라우저에 저장됩니다.
  4. 이후의 요청에서 세션 ID는 데이터베이스와 대조되어 유효한 경우 요청이 처리됩니다.
  5. 사용자가 로그아웃하면 세션은 클라이언트와 서버 양쪽에서 파기됩니다.

 

토큰 기반 인증의 확산과 적용

2010년대 초반 JSON Web Token(JWT)의 개념이 등장 하기 시작했고, 이 시기에 JWT의 기초적인 개념과 구조가 개발되었습니다.

그렇지만 실제로 확산되고 적용된 것은, 2014 년 JWT 가 공식 표준으로 자리를 잡고
2015년 이후 OAuth 2.0과 같은 인증 프레임워크에서 JWT를 지원하기 시작하면서 토큰 기반 인증의 사용이 더욱 널리 퍼졌습니다.

OAuth 2.0은 인증 및 권한 부여 프로토콜로, JWT를 사용하여 액세스 토큰을 발급하고 관리할 수 있게 함으로써 다양한 애플리케이션에서의 채택을 가속화했습니다.

 

현재의 상태

토큰 기반 인증은 웹 애플리케이션, 모바일 애플리케이션, 마이크로서비스 아키텍처, IoT 등 다양한 분야에서 광범위하게 사용되고 있습니다. JWT는 특히 그 간결함과 유연성 덕분에 사실상의 표준으로 자리잡았으며, 많은 개발자들이 API 보안 및 사용자 인증을 위해 이를 사용하고 있습니다.

 

토큰 기반 인증

토큰 기반 인증은 싱글 페이지 애플리케이션, 웹 API, 사물인터넷(IoT)의 증가로 인해 최근 몇 년간 널리 사용되고 있습니다. 토큰을 사용한 인증을 말할 때 일반적으로 JSON 웹 토큰(JWT)을 의미합니다. 토큰을 구현하는 다양한 방법이 있지만, JWT가 사실상의 표준이 되었습니다. 

토큰 기반 인증은 상태를 유지하지 않습니다. 서버는 로그인한 사용자나 발급된 JWT에 대한 기록을 유지하지 않습니다. 대신, 서버는 요청마다 토큰을 받아 요청의 진위 여부를 확인합니다. 토큰은 일반적으로 Bearer {JWT} 형식으로 추가 Authorization 헤더에 포함되어 전송되지만, POST 요청 본문이나 쿼리 매개변수로도 전송될 수 있습니다.

토큰 기반 인증 흐름을 살펴보겠습니다

  1. 사용자가 로그인 자격 증명을 입력합니다.
  2. 서버가 자격 증명이 올바른지 확인하고 서명된 토큰을 반환합니다.
  3. 이 토큰은 클라이언트 측에 저장됩니다. 가장 일반적으로는 로컬 스토리지에 저장되지만, 세션 스토리지나 쿠키에도 저장될 수 있습니다. 
  4. 이후의 요청에는 이 토큰이 추가 Authorization 헤더 또는 위에서 언급한 다른 방법을 통해 포함됩니다.
  5. 서버는 JWT를 디코딩하고 토큰이 유효하면 요청을 처리합니다.
  6. 사용자가 로그아웃하면 토큰은 클라이언트 측에서 파기되며, 서버와의 상호작용은 필요하지 않습니다.


실제로 제가 맡았던 쇼핑몰 프로젝트의 JWT 토큰을 통한 인증 방식의 흐름을 보여드리겠습니다.

1. 사용자가 로그인을 하면, 서버에서 리프레쉬 토큰을 HttpOnly Cookie 에 담아 클라이언트로 보냅니다. 

// 리프레시 토큰을 쿠키에 저장합니다.
response.addCookie(createCookie("reAuthorization", "Bearer+" +refreshToken));

// CookieUtil.java 파일에서 함수
public static Cookie createCookie(String key, String value) {

    Cookie cookie = new Cookie(key, value);
    // 만료 기간을 설정하지 않음으로써 세션 쿠키를 사용. 퍼시스턴트 쿠키를 사용하지 않음.
    // cookie.setMaxAge(60*60*60);
    cookie.setSecure(true);
    cookie.setPath("/");
    cookie.setHttpOnly(true);

    return cookie;
}

2. 클라이언트에서는, 서버로부터 받은 리프레쉬 토큰을 통해서 엑세스토큰을 주기적으로 재발급 받습니다.

3. 주기적으로 Access Token 을 재발급 받을 때, 엑세스토큰을 클라이언트의 로컬스토리지에  Authorization="Bearer {accessToken}" 과 같은 방식으로 위치시킵니다.

// Access token을 주기적으로 갱신하는 컴포넌트
export const PeriodicAccessTokenRefresher = () => {
  const requestCount = useRef(0);
  const navigate = useNavigate();

  useEffect(() => {
    const accessTokenGenerator = async () => {
      try {
        requestCount.current += 1;
        console.log(`Access token request count: ${requestCount.current}`);

        const response = await axios.post(
          `${process.env.REACT_APP_BACKEND_BASE_URL}/reissue/access`,
          {},
          {
            headers: {
              "Content-Type": "application/json",
            },
            withCredentials: true,
          }
        );

        const newAccessToken = response.data["accessToken"];
        extractUserInfoFromAccess(newAccessToken);
      } catch (error) {
        console.error(
          "refresh token이 만료되었거나 존재하지 않습니다. ",
          error
        );

        navigate("/login");
      }
    };

    const interval = setInterval(
      accessTokenGenerator,
      ACCESS_TOKEN_EXPIRATION_PERIOD
    );

    return () => clearInterval(interval);
  }, [navigate]);

  return null;
};

 

로그인 이후의 추가적인 인증 작업

  • 모든 API에 "withCredentials: true" 을 담아서 로그인 중인지에 대한 판단을 서버가 합니다.
    axios , fetch 요청시 withCredentials 를 담아주면, HttpOnly Cookie 를 서버에서 받을 수 있게 됩니다. 

추가적으로 Api 요청 마다 인가 작업

  • Request Header 에 로컬 스토리지에 저장되어 있는 Authorization 을 담아서, 서버의 Controller 단에서 인가 작업을 진행하게 됩니다.

상태 유지와 무상태성의 차이점

  • 상태 유지 : 서버 리소스를 더 많이 사용하지만, 실시간 데이터가 필요한 경우 유리함
  • 무상태성 : 서버 리소스를 적게 사용하며, 확장성과 성능에서 이점이 있음.

토큰 기반 인증 시스템의 이점 

  1. 무상태성(Stateless), 확장성(Scalability), 분리 가능성(Decoupled)

    토큰을 사용하는 가장 큰 장점은 토큰 인증이 상태를 유지하지 않는다는 것입니다.
    백엔드는 토큰 기록을 유지할 필요가 없습니다. JWT는 유효한 JSON인 한 어떤 유형의 메타데이터도 저장할 수 있습니다.
    JWT 스펙에서는 예약된 클레임, 공개 클레임, 비공개 클레임 등 다양한 유형의 클레임을 포함할 수 있도록 명시하고 있습니다.

    서버의 유일한 역할은 로그인 요청이 성공하면 토큰을 서명하고 들어오는 토큰의 유효성을 확인하는 것입니다. 

    세션 기반의 인증은, 로그인한 유저의 상태를 지속적으로 유지해서 실시간으로 바뀌는 유저의 정보와 서버의 데이터를 주고받아야 하기에, 서버에 많은 부하를 주게 됩니다. 하지만 SPA(Single Page Application) 에서 세션 기반 인증은 1초, 2초마다 서버와의 상태를 유지하며 로그인 유저의 정보를 받는 것은 좋지 않습니다.
    하지만 대규모 다중 사용자 온라인 롤플레잉 게임(MMORPG)에서는 세션 기반 인증을 필요로 합니다. 세션 기반 인증이 좋지 않다는 이야기는 아닙니다. 오히려 이러한 게임에서는 지속적으로 사용자의 상태를 추적하고 관리해야 하기 때문입니다. 
    배틀 그라운드, 리니지와 같은 게임 뿐만 아니라, 기업 내부 비즈니스 애플리케이션, 은행 시스템, 보안이 중요한 웹 애플리케이션에서도 세션을 확인해서 사용자의 로그인 상태를 유지해야 합니다. 
  2. 보안성
    클라이언트가 서버로 요청을 보낼 때 더 이상 쿠키를 전달하지 않으므로, 쿠키 사용에 의한 취약점이 사라지게 됩니다.

    엑세스토큰의 주기를 5분, 10분, 30분 등 짧게 설정 한다면 보안성이 올라갈 수 있습니다. 엑세스토큰이 탈취된다고 하더라도, 유효기간이 정해져 있기 때문에 안전할 수 있습니다. 

    하지만 리프레쉬 토큰은 상대적으로 유효기간이 길기 때문에, 보안상 탈취되게 된다면 유저의 개인정보가 위험해질 수 있습니다. 
    그러므로 HttpOnly (자바스크립트를 통해 쿠키를 꺼낼 수 없는 설정) Cookie 로 리프레쉬토큰을 쿠키에 저장하는 방법을 예로 들 수 있겠습니다.

    하지만 토큰 환경의 취약점이 존재할 수 있으므로 이에 대비해야 합니다.
    예를 들면 크로스 사이트 스크립팅(XSS), 과 크로스 사이트 요청 위조(XSRF 또는 CSRF)가 있습니다.
    이에 대한 대비책으로 XSS 공격 방지를 위한 Content Security Policy(CSP), CSRF 방지를 위한 CSRF 토큰 사용을 예로 들 수 있습니다.
  3. 확장성(Extensibility)
    시스템의 확장성을 의미하는 Scalability와 달리 Extensibility는 로그인 정보가 사용되는 분야의 확장을 의미합니다.
    토큰 기반의 인증 시스템에서는 토큰에 선택적인 권한만 부여하여 발급할 수 있으며, OAuth의 경우 Kakao, Google, Naver 등과 같은 소셜 계정을 이용하여 다른 웹서비스에서도 로그인을 할 수 있습니다.

    OAuth2 Client 소셜로그인을 통해, 소셜로그인을 진행하면서 프로젝트 자바 백엔드 서버에서 Java Json Web Token 라이브러리를 통해 JWT 토큰을 직접 발급하고, response 에 리프레쉬토큰을 HttpOnly Cookie 로 넘겨주었습니다. 
    이것이 확장성의 한 예시라고 볼 수 있습니다.
  4. 모바일 환경의 서비스에서 구현이 쉽다. 
    토큰 기반 인증 API는 브라우저와 iOS, Android와 같은 네이티브 모바일 플랫폼에 대해서 세션(쿠키) 기반의 인증보다 구현하기 좋은 서비스를 제공합니다.
    가능은 하지만, 모바일 플랫폼에서 쿠키를 사용하는 데는 많은 제한 사항과 고려 사항이 있습니다. 반면, 토큰은 iOS와 Android 모두에서 구현하기 훨씬 쉽습니다.

 

Refrence

https://dzone.com/articles/cookies-vs-tokens-the-definitive-guide

http://www.opennaru.com/opennaru-blog/jwt-json-web-token-w

https://mangkyu.tistory.com/55