토큰 기반의 사용자 인증

dobby·2024년 8월 26일
0
post-thumbnail

인증(Authentication)과 인가(Authorization)

인증

식별 가능한 정보로 서비스에 등록된 유저의 신원을 입증하는 과정

인가

인증된 사용자에 대하여 자원의 접근권한을 확인하는 것

예시)

  • 간단한 게시판 서비스가 있다고 가정하면 모든 사용자가 글을 읽을 수 있으나 글을 작성하려면 유저 정보가 필요하다.

  • 사용자는 로그인을 통해 서비스에 등록된 유저임을 서버에 알리고 글을 쓸 수 있는 권한을 얻게 된다.

  • 이 과정에서 로그인을 통해 인증을 받게 되고, 서버로부터 접근 권한을 인가받게 되는 것이다.


토큰 기반의 인증 방법

웹에서 사용자를 인증하는 보편적인 방법은 로그인을 통해 인증하는 것이다.
로그인 구현 전략 중 하나는 쿠키와 세션을 이용하는 방법이 있다.

쿠키-세션 방식의 로그인

  • 쿠키-세션 방식의 경우 세션을 서버에 저장해야 하므로 비효율적이다.
  • 서버 확장 시 서버간에 세션을 공유하기 어렵다.

이러한 단점을 해결하기 위한 방법으로 토큰 기반의 사용자 인증 방법을 사용할 수 있다.

JWT(Json Web Token)

토큰이란 간단히 말하면 로그인 이후 서버가 만들어서 사용자에게 넘겨주는 문자열이다.
이 문자열은 사용자 정보가 암호화 되어 있고, 이 토큰을 이용하여 인증된 사용자인지 서버가 판단한다.
이때 사용되는 토큰이 JWT이고 하나의 표준 인증 방식으로 이해할 수 있다.

기존 세션으로의 인증방법은 서버 확장성과 저장 공간 용량에 대한 문제가 있었다.

세션은 서버의 메모리에 생성되는 저장 공간이기에 유저가 한 두명일 때는 메모리에 무리가 가지 않지만 유저가 수천명인 대형 서비스에서는 세션의 양이 많아지는 만큼 메모리에 부하가 걸릴 수 있다.

또한, 서비스의 규모가 커져서 서버를 여러대로 확장 및 분산해야 한다면, 세션을 분산시키는 기술을 따로 설계해야 한다.

이를 해결하기 위해 보통 JWT라는 로그인 방식을 도입한다.


JWT 구조

JWT는 세 파트로 나누어지며, 각 파트는 점으로 구분하여 표현된다.
순서대로 헤더, 페이로드, 서명으로 구성한다.

1) header

  • 토큰의 종류와 해싱 알고리즘의 정보 이렇게 2가지 정보가 담겨있다.
  • typ: 토큰의 타입을 지정한다. 바로 JWT를 말하는 것이다.
  • alg: 해싱 알고리즘을 지정한다. 보통 HMAC-SHA256 혹은 RSA가 사용되며, 이 알고리즘은 토큰을 검증할 때 사용되는 signature 부분에서 사용된다.

2) payload

  • 토큰의 내용물이 인코딩된 부분
  • 즉, 토큰에 담을 정보가 들어있는 부분이다.
  • 여기에 담는 정보의 한 ‘조각’을 클레임(Claim)이라고 부르고, 이는 Json(Key/Value) 형태의 한 쌍으로 이뤄져 있다. 토큰에는 여러개의 클레임을 넣을 수 있다.

jwt에서 header와 payload는 특별한 암호화없이 흔히 사용할 수 있는 base64로 인코딩하기 때문에 서버가 아니더라도 그 값들을 확인할 수 있다.
그래서 jwt는 단순히 '식별 위한' 정보만을 담아두어야 하는 것이다.

3) signature

  • 토큰을 인코딩하거나 유효성 검증을 할 때 사용하는 고유한 암호화 코드이다.
  • 헤더와 내용의 값을 각각 BASE64로 인코딩하고, 인코딩한 값을 개인키를 이용해 헤더에서 정의한 알고리즘으로 해싱을 하고, 이 값을 다시 BASE64로 인코딩하여 생성한다.
  • secret key로 사용되는 일련의 문자열이라고 이해하면 쉽다.

JWT 기반 로그인 동작 과정

  1. 사용자는 id와 password를 입력하고 서버로 요청을 보낸다.
  2. 서버는 db에서 회원을 조회하고 등록된 사용자인지 확인한다.
  3. 등록된 사용자라면 서버는 토큰을 생성하고 프론트로 토큰과 함께 응답을 보낸다.
  4. 응답이 성공적으로 왔다면 로그인(인증)이 성공적으로 이루어진 것이고, 이후 요청에 token을 함께 보낸다.
  5. 서버는 로그인 이후의 요청에 함께 실려온 토큰을 검증하는 과정에서 검증이 성공적으로 끝났다면 권한이 있는 사용자라고 생각하고 요청된 데이터를 응답해준다.(인가)

프론트에서 응답 받은 토큰은 이후 요청시 Authorization header에 토큰 정보를 포함하면 된다.
예를 들어
axios.defaults.headers.common.Authorization = Bearer ${token};
이런 식으로 설정 가능

  1. jwt 토큰을 클라이언트가 서버로 요청과 동시에 전달한다.
  2. 서버가 가지고 있는 공개키를 가지고 signature를 복호화한 다음 header와 payload를 base64 디코딩한 값이 전달받은 header와 payload와 일치한지 확인한다.
    일치한다면 인증을 허용한다.

만약 클라이언트가 payload에 담긴 식별자가 변조된 jwt로 요청을 하더라도, 서버가 애초에 발급했던 signature 안의 payload와 다르기 때문에 인증이 불가능해진다.

stateful 해야하는 세션의 단점을 보완하기 위해 만들어진 jwt는 별도의 세션 저장소를 강제하지 않기 때문에 stateless하여 확장성이 뛰어나고, signature를 통한 보안성까지 갖추고 있다.

JWT 토큰에 대해 설명해주세요.
JWT는 Json 객체에 인증에 필요한 정보들을 담은 후, 비밀키로 서명한 토큰으로 인터넷 표준 인증 방식입니다.
서버는 공개키로 서명을 검증해서 클라이언트를 인가할 수 있습니다.
세션과 달리 토큰은 그 자체를 공개키로 검증만 하면 되기 때문에 서버에 부하를 주지 않고, 여러 서버에 사용할 수 있는 확장성이 있습니다.


토큰 기반 인증방식의 단점

  • 쿠키-세션과 다르게 토큰 자체의 데이터 길이가 길어서, 인증 요청이 많아질 수록 네트워크 부하가 심해질 수 있다.

  • 토큰을 발급하면 만료될 때까지 계속 사용이 가능하기 때문에 토큰이 탈취당하면 대처하기가 어렵다.

  • payload에는 암호화가 되어있지 않기 때문에 민감 정보를 저장할 수 없다.

크리티컬한 문제는 세 번째 단점이 핵심이다.
세션을 통한 인증 방식으론 세션이 탈취당했다고 판단이 된다면, 세션 저장소를 끊어서 탈취당한 세션 ID가 있더라도 세션 저장소에 그 값을 지워 탈취된 후의 상황을 보완할 수 있었다.
하지만, 토큰 기반 인증은 서버에서 데이터를 관리하지 않는 stateless한 상황이기에 문제가 발생하게 된다.

토큰 탈취를 대비한 만료시간 설정하기

토큰이 탈취당할 수도 있는 상황을 대비해 만료시간을 짧게 가져가면 된다.
사용자 인증을 위한 엑세스 토큰을 두고 만료시간을 짧게 두어 탈취당할 시를 대비한다.
그리고 엑세스 토큰이 만료된다면, 리프레시 토큰을 사용해 엑세스 토큰을 재발급해주는 방법이다.

그러니, 엑세스 토큰이 탈취당해도 만료시간이 짧기 때문에 어느정도 대처가 가능하다.
그리고 리프레시 토큰은 클라이언트에서 조작할 수 없도록 httpOnly, secure 등의 쿠키 설정을 추가해주어 보안을 강화한다.

  • 사용자는 로그인 요청을 보내고 성공 시 access token, refresh token을 응답 받는다.

  • 이때 보안을 위해 access token은 만료 시간을 짧게 설정하여 리턴하고, refresh token은 만료시간을 길게 설정하여 httpOnly Cookie로 응답 받는다.

  • 이후 인증된 유저임을 알리기 위해 Authorization header에 access token을 포함하여 요청을 보낸다.

  • access token 만료 시 refresh token을 이용해 access token을 재발급 받는다.

최근에는 웹을 사용하다 보면 카카오, 네이버 등 소셜 로그인 기능을 볼 수 있다.
소셜 로그인 역시 토큰 기반의 로그인 인증 방법이며, Oauth2.0이라고 하는 프로토콜로 유저 정보에 대한 리소스가 프로젝트에서 사용될 백엔드의 DB에 있는 것이 아니라 카카오, 네이버 등 이런 웹서비스에서 제공 받는 것이다.


정리

token은 발급 시 유효기간 동안 되돌릴 수 없고 access token을 탈취당할 시 인증 정보가 모두 담겨있으므로 위험하다.
이를 해결하기 위해 서버에서는 access tokenrefresh token 두 개의 토큰을 발행한다.

  • Access Token: 말 그대로 인증된 유저인지 서버에서 검증하는 토큰이다.
    client 쪽에서 요청 헤더에 담아 보내는 토큰이다.
  • Refresh Token: access token이 만료되거나 잘못된 토큰일 경우 refresh token을 사용해 유저를 검증한다. 만약 refresh token을 검증하여 인증된 유저일 경우 access token을 재발행 해준다.

access token의 유효기간을 짧게 설정하고 refresh token의 유효기간을 길게 설정하여 access token 만료시 refresh token을 검증하여 access token을 재발행 해주는 것으로 보안적으로 안전하게 유저 인증을 관리할 수 있다.

프론트에서 안전하게 Token 관리하기

동작 과정을 살펴 보았듯이 client와 server 간에 인증 요청과 token 응답을 주고 받아야 한다. 이 과정에서 token을 안전하게 관리하는 것이 매우 중요하다.

프론트에서 보통 token을 저장하고 관리할 수 있는 곳은 local storage와 cookie가 있다. (페이지 새로고침이나 브라우저를 재접속해도 로그인을 유지하기 위해) 하지만 두 방식 모두 XSS와 CSRF 공격에 취약할 수 있다.

또 한가지 방법은, 내부 메모리에 저장하는 방법이다.

내부 메모리에 데이터가 위치하기 때문에 외부에서 접근할 수 없어 보안적으로 이점이 있다고 볼 수 있다.

하지만, 이는 새로고침 시 데이터가 날아가기에 서버로부터 토큰을 다시 발급받아와야 하는 큰 문제가 있다.

1) local storage

  • 브라우저 내의 local storage에 세션 id, refreshToken 또는 accessToken을 저장해두면 XSS 취약점을 통해 그 안에 담긴 값을 불러오거나, 불러온 값을 이용해 API 콜을 위조할 수 있다.

2) 쿠키 저장 방식

  • 브라우저의 쿠키에 저장하는 방식으로 http 요청 시 쿠키가 포함되어 서버에 전송하게 된다.

  • 단, 쿠키는 자바스크립트로 접근이 가능하기 때문에 https, samesite, httpOnly, secure 등의 보안 설정이 되어있지 않다면 XSS 취약점을 통해 공격 받을 수 있다.

  • CSRF 공격에도 취약하다.

3) secure, httpOnly 쿠키 저장 방식

  • 서버에서 쿠키를 설정할 때 httpOnlysecure 옵션을 주어 방어하는 방법이다.

  • httpOnly 옵션의 경우 자바스크립트로 접근이 불가능하다.

  • secure 옵션의 경우 https 접속에서만 동작한다.


결론적으로, refresh token을 httpOnly Cookie로 저장해 XSS 공격을 방어하고, samesite 속성을 추가해 CSRF 공격을 방어한다.
또한 access token은 header로 리턴 받아서 프론트에서 관리하는 방법을 사용한다.

이를 통해 CSRF 취약점 공격을 방어하고, XSS 취약점 공격으로 저장된 유저 정보 읽기는 막을 수 있다.


JWT 토큰을 쿠키에 저장했을 때 취약점

  1. 쿠키 조작 및 탈취
    -
    쿠키는 클라이언트 측에서 저장되므로, 악의적인 공격자가 쿠키를 조작하거나 탈취하여 무단으로 접근할 수 있다.
  • JWT가 쿠키에 저장되어 있다면, 해당 토큰을 탈취하면 공격자는 해당 사용자의 권한을 획득할 수 있다.
  1. XSS(Cross-Site Scripting) 공격
    악성 스크립트를 삽입하여 사용자의 브라우저에서 실행될 수 있다.
    XSS 공격으로 인해 공격자는 쿠키를 조작하거나 토큰을 탈취하여 권한을 얻을 수 있다.

  2. CSRF(Cross-Site Request Forgery) 공격
    사용자가 인증된 세션으로 악의적인 요청을 실행하도록 속이는 공격이다.
    공격자는 사용자의 브라우저에서 쿠키를 읽고, 인증된 요청을 위조하여 서버에 보낼 수 있다.

  3. 쿠키의 안전하지 않은 전송
    JWT를 쿠키에 저장할 때, HTTPS를 통해 안전하게 전송되지 않으면 중간자 공격 등의 위협에 노출될 수 있다.


브라우저 관련 보안 취약점

1) XSS(Cross-Site Scripting) 공격

  • 공격자(해커)가 클라이언트 브라우저에 악의적인 스크립트를 삽입해 실행하는 공격이다.

  • 보통 javascript같은 브라우저가 읽을 수 있는 코드를 사용하여 서버에서 스크립트를 실행하거나, url에 javascript를 적어 클라이언트에서 스크립트 실행이 가능하다면 공격자가 사이트에 스크립트를 삽입해 XSS 공격을 할 수 있다.

  • 사용자가 의도치 않은 명령을 실행시키거나 쿠키, 세션 등 민감한 정보를 탈취할 수 있다.

방어 방법
일반적으로 XSS 공격은 브라우저가 애플리케이션에 속한 스크립트와 그렇지 않은 악의적인 스크립트를 구분하지 못할 때 생기는 취약점이기 때문에, 웹 브라우저에서 사용자 입력값과 출력값을 철저히 검증해서 예방할 수 있다.

입력값에 대해 악의적인 스크립트가 있는지 체크하는 validation을 추가하고, 출력할 때는 특수문자 이스케이프 처리를 추가해서 예방하는 방법이 있다.

또한 Anti XSS 라이브러리를 제공해주는 회사들이 많기 때문에, 이 라이브러리를 서버단에 추가하고, 사용자들은 각자 브라우저에서 악성 스크립트가 실행되지 않도록 확장앱을 설치해서 방어할 수 있다.


2) CSRF(Cross Site Request Forgery) 공격

  • 사이트간 위조 요청의 줄임말로 공격자가 다른 사이트에서 우리 사이트의 API 콜을 요청해 실행하는 공격이다.

  • 사용자가 웹사이트에 로그인한 상태에서 사이트간 요청 위조 공격 코드가 삽입된 페이지를 열면, 공격 대상이 되는 웹 사이트는 위조된 공격 명령이 믿을 수 있는 사용자로부터 발송된 것이라고 판단하게 되어 공격에 노출된다.

토큰 저장 방식으로만 모든 보안 취약점을 막는 것은 불가능하지만, 저장 위치에 따른 취약점을 인식하고 저장위치를 결정하는 것이 중요하다.

방어 방법
CSRF 토큰을 사용해 매 요청마다 유일한 토큰 값을 발급하여 요청의 유효성을 검증하거나, SameSite 쿠키 속성을 설정하여 쿠키를 다른 도메인에서 전송되지 않도록 제한하여 예방할 수 있다.

XSS 공격이 무엇이고, 방어하는 방법을 설명해주세요.
XSS는 공격자가 클라이언트 브라우저에 악의적인 스크립트를 삽입해 실행하여 사용자 정보를 탈취하는 공격 방식입니다.
보통 javascript같은 브라우저가 읽을 수 있는 코드를 사이트에 삽입해 XSS 공격을 할 수 있습니다.
일반적으로 XSS 공격은 브라우저가 애플리케이션에 속한 스크립트와 그렇지 않은 악의적인 스크립트를 구분하지 못할 때 생기는 취약점이기 때문에, 웹 브라우저에서 사용자 입력값과 출력값을 철저히 검증해서 예방할 수 있습니다.
입력값에 대해 악의적인 스크립트가 있는지 체크하는 validation을 추가하고, 출력할 때는 특수문자 이스케이프 처리를 추가해서 예방하는 방법이 있습니다.
또한 Anti XSS 라이브러리를 제공해주는 회사들이 많기 때문에, 이 라이브러리를 서버단에 추가하고, 사용자들은 각자 브라우저에서 악성 스크립트가 실행되지 않도록 확장앱을 설치해서 방어할 수 있습니다.

CSRF 공격이 무엇이고, 방어하는 방법을 설명해주세요.
CSRF는 사이트간 위조 요청의 줄임말로, 공격자가 다른 사이트에서 우리 사이트의 API 콜을 요청해 실행하는 공격입니다.
CSRF 토큰을 사용해 매 요청마다 유일한 토큰 값을 발급하여 요청의 유효성을 검증하거나, SameSite 쿠키 속성을 설정하여 쿠키를 다른 도메인에서 전송되지 않도록 제한하여 예방할 수 있습니다.


SOP와 CORS

SOP

SOP는 한 Origin에서 로드된 문서 또는 스크립트가 다른 Origin의 리소스와 상호작용할 수 있는 방법을 제한하는 중요한 보안 메커니즘이다.

보안을 위협하는 문서를 격리하여, 보안 위협으로부터 보호할 수 있다.

한마디로, 웹 브라우저에서 동작하는 프로그램은 로딩된 위치에 있는 리소스만 접근할 수 있다는 정책이다.

두개의 URL이 존재할 때 프로토콜, 포트(지정된 경우), 호스트가 동일한 경우 두 URL의 Origin이 같다.


SOP 허용

SOP가 적용되지 않는 Cross-Origin의 예는 다음과 같다.

  • <img> 태그로 다른 도메인의 이미지 파일 요청
  • <link> 태그로 다른 도메인의 CSS 요청
  • <script> 태그로 다른 도메인의 js 파일 요청

SOP 적용

SOP가 적용되는 Cross-Origin의 예는 다음과 같다.

  • Ajax(XMLHttpRequest, XHR)
  • Fetch API

SOP 완화

  • 개발 시 외부 API를 사용하는 경우도 많고, 클라이언트와 서버를 분리하여 개발하는 경우도 많다.
  • Ajax가 널리 사용되면서 XMLHttpRequest에 대해서 Cross-Site HTTP Request의 필요성이 증가했다.

CORS

CORS를 설정한다는 건 ‘출처가 다른 서버 간의 리소스 공유’를 허용한다는 것이다.

SOP가 서로 다른 출처일 때 리소스 요청과 응답을 차단하는 정책이라면, CORS는 반대로 서로 다른 출처라도 리소스 요청, 응답을 허용할 수 있도록 하는 정책이다.
그래서 우리가 만나는 에러는 CORS가 가능하도록 뭔가 설정하라는 내용으로 이루어져 있다.

웹의 발달과 CORS

예전에는 프론트엔드와 백엔드를 따로 구성하지 않고 한 번에 구성해서 모든 처리가 같은 도메인 안에서 가능했다.
그래서 다른 출처로 요청을 보내는 게 의심스러운 행위로 보일 수밖에 없었다.
그런데 시간이 지나 클라이언트에서 API를 직접 호출하는 방식이 당연해지기 시작했다.
그런데 보통 클라이언트와 API는 다른 도메인에 있는 경우가 많다.
그래서 CORS 정책이 생겼다.
출처가 다르더라도 요청과 응답을 주고받을 수 있도록 서버에 리소스 호출이 허용된 출처(Origin)를 명시해 주는 방식으로 사용한다.

SOP와 CORS에 대해서 설명해주세요.
CORS는 클라이언트 브라우저에서 현재 접속한 사이트 외에 다른 도메인에 접근할 수 있도록 처리해주는 웹 브라우저 표준 기술입니다.
브라우저에는 크로스사이트 스크립트 등의 이유로 보안상 스크립트를 사용해서 다른 도메인으로 접근하는 것을 제한합니다.
이를 Same Origin Policy라고 하는데, 이러한 정책 제한을 넘어서기 위해 CORS가 필요합니다.
예를 들면 백엔드 서버에 프론트엔드에 대한 CORS 설정을 허용해줘서 프론트가 백엔드 API를 사용할 수 있게 해줘야 합니다.

SQL Injection 공격이 무엇이고, 방어하는 방법을 설명해주세요.
SQL injection은 응용 프로그램 보안 상의 허점을 의도적으로 이용해, 악의적인 SQL문을 실행되게 함으로써 데이터베이스를 비정상적으로 조작하는 코드 인젝션 공격 방법입니다.
웹 브라우저에서 들어오는 입력값에 대해 악성 SQL 패턴에 대한 Validation을 추가하고, 데이터베이스에 평소 사용자에 의해 발생하는 SQL 패턴이 아닌 비정상적인 SQL이 발생하는지 주기적으로 모니터링하고 비상 알람을 걸어두는 방식을 선택할 수 있습니다.
또한 클라이언트 측에서 자바스크립트에 대한 필터를 처리했더라도 하지 않았다고 가정하여 입력값을 한 번 더 필터한 후, prepared Statement 구문을 사용하여 사용자의 입력값이 데이터베이스의 파라미터로 들어가기 전에 DBMS가 사용자의 입력값을 문자열로 인식하게 하여 공격 쿼리가 들어간다 하더라도 공격자의 의도대로 작동하지 않도록 합니다.

Connection timeout과 Read timeout에 대해 설명해주세요.
커넥션 타입아웃은 TCP 연결 확립이 수행되는데 걸리는 최대 시간입니다.
이 시간을 넘기게 되면 연결할 수 없는 것으로 판단하고 에러가 발생합니다.
리드 타입아웃은 요청과 응답이 수행되는데 걸리는 최대 시간입니다.
이 시간을 넘기게 되면 데이터를 받을 수 없는 것으로 판단하고 에러가 발생합니다.

그렇다면 타임아웃이 필요한 이유에 대해 설명해주세요.
타임아웃이 필요한 이유는 리소스를 절약하기 위해서입니다.
서버는 여러 클라이언트와 동시에 연결을 생성합니다.
응답 시간이 길어지는 연결이 많으면 리소스가 모두 소진되어 장애로 이어질 수 있습니다. 타임아웃을 사용하면 오래 걸리는 요청을 중단하고 다른 요청을 받을 수 있기 때문에 타임아웃이 필요합니다.

profile
성장통을 겪고 있습니다.

0개의 댓글