Authentication과 Authorization에 대해

rada·2025년 8월 17일
0

개발

목록 보기
51/53

Authentication

로그인하는 절차를 말하며 로그인을 성공하면 그 사람은 authenticator가 된다. 로그IN은 아이디 비밀번호를 아는 사람은 누구나 들어갈 수 있다.

Authorization

로그인 후 그 사람이 관리자냐 일반사용자냐 슈퍼유저나 특정개인이냐를 구분하는 절차를 말하며,
로그ON은 아이디 비밀번호를 아는 사람이 특정인이라고 간주한다.

HTTP

HTTP는 HTML 문서와 같은 리소스들을 가져올 수 있도록 해주는 프로토콜이다. HTTP는 웹에서 이루어지는 모든 데이터 교환의 기초이며, 클라이언트-서버 프로토콜이기도 하다. 
(
클라이언트-서버 프로토콜 : 웹브라우저인 수신자 측에 의해 요청이 초기화되는 프로토콜)

HTTP 특성

HTTP는 connectionless하고 stateless하다는 특성이 있다.

  • Connectionless : 한 번 통신이 이뤄지고 난 후에 연결이 바로 끊어진다
  • Stateless : 이전 상태를 유지/기억하지 않는다

JWT란

JSON Web Token은 선택적 서명 및 선택적 암호화를 사용하여 데이터를 만들기 위한 인터넷 표준으로, 페이로드는 몇몇 클레임 표명을 처리하는 JSON을 보관하고 있다. 토큰은 비공개 시크릿 키 또는 공개/비공개 키를 사용하여 서명된다.

  • Gmail 인증 서버에 로그인 정보(아이디, 비밀번호)를 제공한다.
  • 인증에 성공할 경우, JWT를 발급받는다.
  • 새로 사용할 애플리케이션은 JWT를 사용해 해당 사용자의 이메일을 읽거나 사용할 수 있다.

JWT 종류

사용자(클라이언트)가 처음 인증을 받게 될 때(로그인), 액세스 토큰과 리프레시 토큰 두 가지 모두 받게 된다.

Access Token

액세스 토큰은 보호된 정보(이메일, 연락처, 사진 등)들에 접근할 수 있는 권한 부여에 사용한다.
액세스 토큰은 다른 사람이 탈취하여 사용하면 문제가 발생할 수 있어, 비교적 짧은 유효 기간을 가지도록 하여 탈취되더라도 오랫동안 사용할 수 없도록 해야 한다.

Refresh Token

액세스 토큰의 유효기간이 만료되면, 리프레시 토큰을 사용하여 새로운 액세스 토큰을 발급받는다.
이때 사용자는 다시 로그인 인증을 할 필요가 없다.

유효기간이 긴 리프레시 토큰은 다른 사람에게 탈취되면 더 큰 문제가 발생할 수 있다.
다른 사람이 리프레시 토큰을 이용하여 액세스 토큰을 발급받을 수 있게 되고, 사용자는 피해를 입을 수 있다.
따라서, 웹 애플리케이션은 리프레시 토큰을 사용하지 않는 곳이 많다.

JWT 구조

JWT에는 Header, Payload, Signature를 .으로 구분한 형태로 이루어져 있다.

토큰이 어떤 종류의 토큰인지(JWT), 어떤 알고리즘으로 Sign 할지 정의한다. JWT는 JSON 포맷 형태로 정의한다.

Payload

서버에서 활용할 수 있는 사용자의 정보(claim)를 포함하고 있다.
어떤 정보에 접근 가능한지에 대한 권한을 담을 수도 있고, 사용자의 이름 등 필요한 데이터를 담을 수 있다.
Payload는 Signature를 통해 유효성이 검증될 정보이지만, 민감한 정보는 담지 않는 것이 좋다.

Signature

Signature에서 사용하는 알고리즘은 대표적으로 RS256(공개키/개인키)와 HS256(비밀키(대칭키))가 있다.

Header와 Payload 부분이 base64로 인코딩 되어 완성되면, Signature에서는 원하는 비밀 키(Secret Key)와 Header에서 지정한 알고리즘을 사용하여 Header와 Payload에 대하여 단방향 암호화를 수행한다.
암호화된 메시지는 토큰의 위변조 유무를 검증하는 데 사용된다.

즉, Header에서 선언한 알고리즘에 따라 key는 개인키가 될 수도 있고 비밀키가 될 수도 있다. 개인키로 서명했다면 공개키로 유효성 검사를 할 수 있고, 비밀키로 서명했다면 비밀키를 가지고 있는 사람만이 암호화 복호화, 유효성 검사를 할 수 있다.

토큰기반 인증 절차

  1. 클라이언트가 서버에 아이디와 비밀번호를 담아 로그인 요청을 보낸다.

  2. 서버에서는 아이디와 비밀번호가 일치하는지 확인하고 클라이언트에게 보낼 암호화된 토큰을 생성한다.

    • 액세스 토큰과 리프레시 토큰 모두 생성한다.
    • 토큰에 담길 정보(Payload)는 사용자를 식별할 정보와 사용자 권한 정보 등이 될 수 있다.
      (두 종류의 토큰이 같은 정보를 담을 필요는 없다.) 
  3. 토큰을 클라이언트에게 전송하면 클라이언트는 토큰을 저장한다.

    • 로컬 저장소, 세션 저장소, 쿠키 등 
  4. 클라이언트가 HTTP Header 또는 쿠키에 토큰을 담아 요청(request)을 전송한다.

    • Bearer authentication을 이용한다.
  5. 서버는 토큰을 검증하여 발급한 토큰과 일치할 경우 클라이언트의 요청에 대한 응답(response)을 보낸다.

일반적으로 클라이언트는 위와 같이 HTTP 요청 Header에 'Authorization' 항목에 JWT 토큰을 넣어 전송하게 되는데, 이때 Bearer 스키마를 토큰 앞에 명시한다. 여기서 Bearer는 '소유자'라는 뜻으로 JWT(또는 OAuth)토큰을 사용했으며, 토큰의 정보를 보고 권한을 부여하라는 의미를 가진다. (RFC6750: https://datatracker.ietf.org/doc/html/rfc6750)

참고로 Bearer 외에 다른 타입으로는 Basic(RFC7617), Digest(RFC7616), HOBA(RFC7486) 등이 있다.

따라서 토큰은 아이디/비밀번호와 같이 보안적으로 주의가 필요하며 토큰이 필요없어질 때는 제거해야하는데 이 부분은 본문 아래쪽에서 별도로 다룬다. 그리고 서버에 따라 HTTP 헤더에 길이 제한이 있을 수 있으므로 JWT 토큰이 지나치게 비대해 지는 것 또한 주의해야한다. (SpringBoot WEB에서 기본적으로 사용하는 ApacheTomcat의 경우 HTTP Header를 8kb까지로 제한)

JWT를 이용한 인증의 장 단점

장점

  1. JWT는 무상태(Stateless) 아키텍처를 지원한다. 무상태 아키텍처는 서버 측에 클라이언트 상태 정보를 저장하지 않는 방식이며 상태를 유지하지 않고(Statless), 확장에 용이한(Scalable) 애플리케이션을 구현할 수 있다.
    • 서버는 클라이언트에 대한 정보를 저장할 필요가 없다.
    • 토큰이 정상적으로 검증되는지만 판단한다.
    • 클라이언트는 요청을 전송할 때마다 토큰을 헤더에 포함시킨다.
  2. 클라이언트가 요청(request)을 전송할 때마다 자격 증명 정보를 전송할 필요가 없다.
    • HTTP Basic 같은 인증 방식은 request를 전송할 때마다 자격 증명 정보를 포함해야 한다.
      하지만, JWT는 토큰이 만료되기 전까지는 한 번의 인증만 수행하면 된다.
  3. 인증을 담담하는 시스템을 다른 플랫폼으로 분리하는 것이 용이하다.
    • 사용자의 자격 증명 정보를 직접 관리하지 않고, Github, Google 등의 다른 플랫폼의 자격 증명 정보로 인증하는 것이 가능하다.
    • 토큰 생성용 서버를 만들거나, 다른 회사에서 토큰 관련 작업을 맡기는 것 등 다양한 활용이 가능하다.
  4. 권한 부여에 용이하다
    • 토큰의 Payload(내용물) 안에 해당하는 사용자의 권한 정보를 포함하는 것이 용이하다.

단점

  1. Payload는 디코딩이 용이하다.
    • Payload는 base64로 인코딩 되기 때문에 토큰을 탈취하여 디코딩하면 토큰 생성 시 저장한 데이터를 확인할 수 있다.
    • Payload에는 민감한 정보를 포함하지 않아야 한다.
  2. 토큰의 길이가 길어지면 네트워크에 부하를 줄 수 있다.
    • 토큰이 저장하는 정보의 양이 많아질수록 토큰의 길이는 길어진다.
    • 따라서 요청(request)이 발생할 때마다 길이가 긴 토큰을 함께 전송하면 네트워크에 부하를 줄 수 있다.
  3. 토큰은 자동으로 삭제되지 않는다.
    • 한 번 생성된 토큰은 자동으로 삭제되지 않기 때문에 토큰 만료 시간을 반드시 추가해야 한다.
    • 토큰이 탈취된 경우 토큰의 기한이 만료될 때까지 탈취자가 해당 토큰을 정상적으로 이용할 수 있으므로 만료 시간을 길게 설정하지 않아야 한다.

JWT 저장 방법

Cookies

서버(Set-Cookie)에서 클라이언트에게 JWT 토큰을 쿠키에 저장하여 전송할 수 있다. 쿠키는 HTTP 요청 헤더에 자동으로 포함되므로, 클라이언트 및 서버 모두에서 쉽게 액세스할 수 있고 Secure 및 HttpOnly 플래그를 사용하여 보안을 강화할 수 있다. Secure 플래그는 HTTPS 연결을 통해만 쿠키가 전송되도록 보장하고, HttpOnly 플래그는 JavaScript에서 쿠키에 접근하는 것을 방지하여 XSS 공격을 예방하지만, 모든 HTTP 요청에 자동으로 포함되기 때문에 요청을 위조하는 CSRF 공격으로부터 안전하지 않다.

  • cookie + HttpOnly(XSS 방어)
res.cookie('token', token, {httpOnly: true, secure: true, maxAge:3600000});// 1시간 유효기간
  • cookie + secure(HTTPS 연결에서만 전송)
res.cookie('token', token, {secure: true, maxAge:3600000});// 1시간 유효기간
  • cookie + sameSite(CSRF 방어)
res.cookie('token', token, {sameSite:'strict', secure: true, maxAge:3600000});// 1시간 유효기간

HTTP 인증 헤더

HTTP 인증 헤더는 토큰을 HTTP 요청 헤더에 직접 포함시켜 토큰이 쿠키 저장소에 노출되는 것을 방지한다. 또한 쿠키는 브라우저가 자동으로 모든 요청에 포함시키므로 CSRF 공격에 취약할 수 있는 반면 HTTP 인증헤더를 사용하면 CSRF(Cross-Site Request Forgery)를 방지 할 수 있다.
하지만, 요청 시 매번 클라이언트 코드에서 헤더에 붙여야 하며, 저장 위치는 개발자가 따로 관리해야 한다 (로컬 스토리지, 메모리 등).

Authorization: Bearer <token>

로컬 스토리지

클라이언트 측에서 JWT 토큰을 브라우저의 로컬 스토리지에 '영구 저장'할 수 있다. 이 방법은 간단하고 사용하기 쉽지만, 보안상의 이유로 추천되지 않는다. 로컬 스토리지에 저장된 토큰은 JavaScript 코드에서 쉽게 액세스할 수 있으며, XSS(크로스 사이트 스크립팅) 공격에 취약할 수 있다.

localStorage.setItem("jwt", token)

세션 스토리지

로컬 스토리지와 유사하지만, 세션 스토리지에 저장된 데이털는 세션이 종료되면 제거된다. 세션 스토리지를 사용하면 토큰이 브라우저를 닫거나 탭을 닫을 떄 자동으로 삭제되며 보안상 로컬 스토리지보다 안전하나, xss공격에 취약하다.

sessionStorage.setItem("jwt", token)

웹 스토리지 API

웹 스토리지 API는 localStorage 및 sessionStorage를 묶어 부르며, 이를 통해 클라이언트 측에서 데이터를 저장하고 검색할 수 있다. Key-Value 형태로 저장하며, JSON 직렬화가 필요하다. 보안 측면에서는 쿠키나 HTTP 인증 헤더를 사용하는 것이 더 안전하다.

메모리

JWT를 브라우저의 변수나 React/Zustand/Redux 같은 상태 관리에만 저장한다. 메모리에만 저장되므로 XSS 공격이 성공하더라도 토큰에 접근하기 어렵고, 브라우저 저장소에 접근하는 오버헤드가 없어 성능이 약간 향상된다. 토큰을 직접 관리할 수 있어 유연성이 높다.
그러나 페이지를 새로고침하면 토큰이 사라지며, 여러 탭이나 창에서 공유되지 않고, SPA(Single Page Application)에서만 효과적으로 작동한다.

let token; or zustand.setState({token})
// JavaScript 변수에 저장 (클로저 활용)
const createTokenManager = () => {
  let accessToken: string | null = null;
  
  return {
    setToken: (token: string) => {
      accessToken = token;
    },
    getToken: () => accessToken,
    clearToken: () => {
      accessToken = null;
    }
  };
};

export const tokenManager = createTokenManager();

하이브리드

현대적인 웹 애플리케이션에서는 보안과 사용자 경험의 균형을 위해 하이브리드 접근법이 권장된다.

짧은 수명을 가진 Access Token은 메모리에만 저장하고,
긴 수명을 가진 Refresh Token은 HttpOnly 쿠키에 저장한다.

하이브리드 접근법을 통해 XSS 및 CSRF 공격으로부터 더 나은 보호를 제공하면서도 사용자 경험을 유지할 수 있다.

  1. 로그인 성공 → Refresh Token을 HttpOnly Cookie에 저장.
  2. Access Token은 JS 메모리에만 저장.
  3. Access Token 만료 시 → Refresh Token으로 새로운 Access Token 발급.
// 액세스 토큰을 위한 메모리 저장소
let inMemoryToken: string | null = null;

// 액세스 토큰 관리
const tokenService = {
  setAccessToken: (token: string) => {
    inMemoryToken = token;
  },
  getAccessToken: () => inMemoryToken,
  clearAccessToken: () => {
    inMemoryToken = null;
  }
};

// 리프레시 토큰은 서버에서 HttpOnly 쿠키로 설정됨
// 로그인 API 호출 예시
const login = async (credentials: { email: string; password: string }) => {
  const response = await fetch('/api/auth/login', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(credentials),
    credentials: 'include' // 쿠키를 포함하기 위해 필요
  });
  
  if (response.ok) {
    const data = await response.json();
    tokenService.setAccessToken(data.accessToken);
    return true;
  }
  
  return false;
};

[참고글]
https://velog.io/@chuu1019/%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90-JWTJson-Web-Token
https://y-dev.tistory.com/64
https://mori29.tistory.com/25
https://datatracker.ietf.org/doc/html/rfc6750
https://auth0.com/docs/get-started/applications/signing-algorithms

[jwt test]
https://jwt.io/

profile
So that my future self will not be ashamed of myself.

0개의 댓글