TIL | React (throttling, debouncing, 인증/인가 - 쿠키, 세션, 토큰 JWT ...)

·2023년 7월 9일

TIL # WIL

목록 보기
22/65

23.07.07

1. throttling & debouncing

throttling & debouncing : 짧은 간격으로 연속해서 이벤트가 발생했을 때 과도한 이벤트 핸들러 호출을 방지하는 기법들

1-1. throttling 쓰로틀링

throttling : 이벤트가 짧은 시간 연속으로 발생하지만 이들을 일정시간 단위(= delay)로 그룹화하여 함수를 이벤트 그룹의 처음이나 마지막에만 호출되도록 하는 것
ex. 무한스크롤

// 반복적인 이벤트 동안, delay 간격으로 function 호출
const throttle = (delay) => {
  if (timerId) {
    // timerId가 있으면 바로 함수 종료
    return;
  }

  // 첫번째 콘솔
  console.log(`API요청 실행! ${delay}ms 동안 추가요청은 안받습니다`);
  timerId = setTimeout(() => {
      // 두번째 콘솔
    console.log(`${delay}ms 지남 추가요청 받습니다!`);
    timerId = null;
  }, delay);
};

정상적일 때

  1. 처음에 timerIdnull, 그러므로 if문을 지나고 아래로 내려가 첫번째 콘솔이 출력됨
  2. 그리고 setTimeout으로 들어가서 delay 시간만큼 지나고 두번째 콘솔이 출력되는 구조

이벤트가 짧은 시간 연속으로 발생할 때

  1. 처음에 timerIdnull, 그러므로 if문을 지나고 아래로 내려가 첫번째 콘솔이 출력됨
  2. 그리고 setTimeout으로 들어가서 delay 시간이 지나기 전에 사용자가 다시 함수를 호출
  3. 그 사이에 timerIdsetTimeout으로 할당되어있기 때문에(= 2초) if문 조건에 의해 return되어 함수가 바로 종료됨 => 지정된 delay 내 함수가 한 번만 호출되도록 보장
  4. 그리고 그동안 clearTimeout 된 것은 아니니 2초 후에 2번째 콘솔이 출력되고 timerId는 다시 null되는 순서

1-2. debouncing 디바운싱

debouncing : 짧은 시간 간격으로 연속해서 이벤트가 발생(= bouncing)하면 함수를 호출하지 않다가(= de) 마지막 이벤트로부터 일정 시간(= delay)이 경과한 후에 함수를 한 번만 호출하도록 하는 것
ex. 입력값 실시간 검색, 화면 resize 이벤트

// 반복적인 이벤트 이후, delay가 지나면 function 호출
const debounce = (delay) => {
  if (timerId) {
    // 할당되어 있는 timerId에 해당하는 타이머 제거
    clearTimeout(timerId);
  }
  timerId = setTimeout(() => {
    // 콘솔
    console.log(`이제 요청으로부터 ${delay}ms 지났으므로 API 요청 실행!`);
    timerId = null;
   }, delay);
};

정상적일 때

  1. 처음에 timerIdnull, 그러므로 if문을 지나고 아래로 내려가 setTimeout으로 들어감
  2. 그리고 delay 시간만큼 지나고 콘솔이 출력되는 구조

이벤트가 짧은 시간 연속으로 발생할 때

  1. 처음에 timerIdnull, 그러므로 if문을 지나고 아래로 내려가 setTimeout으로 들어감
  2. 그리고 delay 시간만큼 지나기 전에 사용자가 다시 함수를 호출한다면 그 사이에 timerIdsetTimeout으로 할당되어있기 때문에 if문으로 올라가 clearTimeout이 실행되어 timerId는 다시 null이 됨

 

Q. 근데 만약 throttling 함수를 호출하고 페이지 이동을 한다면 ?
A. 이벤트는 유지되고 있음 => 메모리 누수

1-3. 메모리 누수

메모리 누수 : 메모리가 사용되지 않는 시점에서도 메모리를 반환하지 않고 계속 점유하고 있는 현상

let timerId = null;

useEffect(() => {
  return () => {
    // 언마운트 시
    if (timerId) {
      clearTimeout(timerId);
    }
  };
}, []);

메모리 누수를 막기 위해 useEffect를 사용해서 언마운트 될 때(= 홈이라는 컴포넌트가 사라질 때) clearTimeout을 실행해서 timerId를 제거해주는 식으로 메모리 누수를 막을 수 있음

만약 메모리 누수를 막지 않는다면 프로그램이 비정상적으로 작동하거나 크래시가 발생하는 등의 문제가 생길 수가 있으니 신경써서 점검해야 함 !

1-3. lodash 적용 & useCallback을 써야하는 이유

lodash : _ 라이브러리

라이브러리이기 때문에 yarn add lodash로 설치해야 함 => lodash에는 debounce, throttle 함수를 제공하고 있어서 lodash를 사용하게 된다면 해당 함수를 직접 구현할 필요가 없음

debounce 기본형

// 첫번째 인자 함수, 두번째 인자 시간
_.debounce( func, wait, options )

 
debounce 커스텀

// 첫번째 인자 함수, 두번째 인자 시간
const debounce = (callback, delay) => {
  let timerId = null;
  // debounce 함수 내부에 있는 closure라는 특성 때문에 내부 변수 뿐만 아니라 외부 변수(ex. timerId)를 참조할 수 있음
  return (...args) => {
    if (timerId) {
      clearTimeout(timerId);
    }
    timerId = setTimeout(() => {
      callback(...args);
    }, [delay]);
  };
};

// useCallback 훅을 통해 메모이제이션을 해놔서 렌더링이 다시 일어나더라도 새롭게 함수가 반환되는게 아니라
// debounce 함수 내부에 closure 함수 특성 부분까지를 기억하고 있어서(= 여전히 똑같은 값을 참조하고 있기 때문에)
// 기억하고 searchText를 바꿔줄 수 있는 것 !
const handleSearchText = useCallback(
  // debounce 함수가 바로 실행됨
  debounce((text) => {
   setSearchText(text);
  }, 2000),
  []
);

closure mdn 문서


2. 인증 / 인가

인증 Authentication : 등록된 회원인지 확인하는 절차 (ex. 로그인)
인가(인정하여 허가) Authorization : 특정 리소스에 접근할 수 있는 권한을 확인하는 절차 (ex. 네이버 카페에서 등급에 따라 글을 볼 수 있는 기능)

http 프로토콜 통신의 특징 2가지

  1. 무상태 (Stateless) : 서버와 클라이언트 사이의 상태가 없는 것 => 서버는 클라이언트의 상태를 기억하지 않음 => 왜 ? 클라이언트가 매번 요청할 때 상태값을 가져오기 때문에 서버는 클라이언트의 상태를 별도로 기억할 필요가 없음 => 이런 특성으로 인해 서버를 확장시키기 용이(= Scale-Out)
  2. 비연결성 (Connectionless) : 서버와 클라이언트는 연결되어 있지 않음 => 즉, 요청과 응답이 끝난 후에는 연결이 끝나고 또 다시 새로운 요청이고 응답임 => 이러한 특성으로 인해 최소한의 서버 자원으로 서버를 유지할 수 있음
    => 그러나 채팅과 같은 요청 <-> 응답이 잦은 서비스는 비연결성이 비효율적

2-1. 쿠키

쿠키 cookie : 브라우저가 key-value 형태의 텍스트 파일로 저장하고 있는 것

쿠키의 특징

  • 무상태와 비연결성이라는 http 통신의 특징에도 불구하고 마치 서버가 클라이언트의 인증 상태를 기억하는 것처럼 구현할 수 있는 수단으로 사용
  • 쿠키는 별도로 삭제처리하거나 유효기간이 만료되지 않은 이상 서버와 통신할 때 자동으로 주고 받고 => 이로 인해 인증 상태를 기억하는 것처럼 구현되는 것
  • 브라우저(= 클라이언트)가 서버에 요청했을 때, 서버는 응답시 header 안에 set-cookie 속성으로 쿠키 정보를 담아주고 (개발자도구 → 네트워크 → 쿠키), 응답받은 브라우저는 쿠키를 브라우저에 자동으로 저장 (개발자도구 → 애플리케이션 → 저장용량 → 쿠키)
  • 서버에 http 요청 할 때 마다 브라우저에 저장되어 있는 쿠키는 자동으로 서버에 보내짐 => 단, 동일한 Origin 또는 CORS를 허용하는 Origin에만 쿠키를 보냄
    (ex. 유튜브 서버에서 받은 쿠키는 유튜브 이용 시에만 주고 받을 수 있음)

 
그렇다면 여기서 Origin이란 ? 출처, 여기서는 url의 출처 => url 중에서 'protocol + host + port' 부분을 의미

그렇다면 여기서 CO(origin)RS란 ? Cross Origin Resource Sharing로 다른 출처(= origin)에 리소스 요청을 허용하는 정책

  • 기본적으로 브라우저는 보안상의 이유로 Same Origin Policy(SOP)이 원칙이라 같은 출처(= origin)에만 리소스 요청을 허용하는데, 서버와 클라이언트 상호간 CORS 설정을 통해 합의가 되어있다면 예외적으로 API 요청이 가능함
  • 또한 쿠키는 클라이언트에서 직접 추가, 수정, 삭제 가능함 => 서버에서 쿠키를 검증하지 않는다면 보안에 매우 취약해짐

이처럼 로그인을 통해 등록된 회원임을 ‘인증’할 때, 서버가 클라이언트 쪽으로 sessionId가 key인 쿠키를 보내고 클라이언트가 ‘인가’가 필요한 데이터를 요청할 때, 클라이언트에서 서버 쪽으로 쿠키가 자동으로 보내짐

2-2. 세션

세션 : 사용자(= 클라이언트)와 서버 간 연결이 활성화 된 상태 (or 인증이 유지되고 있는 상태)를 의미

물론 http는 비연결성이지만 쿠키와 세션을 이용해 계속적인 인증처리가 자동으로 되면 연결이 활성화 된 상태로 나타날 수 있음
=> 클라이언트는 서버에 요청하여 로그인 성공 (이때 자동으로 쿠키 보내짐) -> 서버는 세션(sessionId) 생성 및 저장 (key-value) -> key(sessionId)를 브라우저에 쿠키로 보내 응답

쿠키-세션 인증(Authentication) 방식 : 쿠키와 세션을 결합해서 클라이언트와 서버간의 통신(ex. 인증과 인가)하는 방식으로 CORS 옵션 중요함

로그인/회원가입 시 세션 인증

  1. 클라이언트가 서버에 로그인/회원가입 ‘요청’
  2. 서버는 DB에 클라이언트가 보낸 id, password를 저장
  3. DB는 받은 id, password에 해당하는 회원의 데이터를 찾다가 있으면 서버에 그에 해당하는 회원 객체를 반환
  4. 서버는 그 회원에 대한 sessionId 생성하고 sessionId value에 회원 정보를 저장
  5. 그리고 그 sessionId를 서버는 다시 클라이언트에게 응답(response)시 Set-Cookie의 속성에 담아서 보내줌
  6. 클라이언트(= 브라우저)는 자동으로 받은 쿠키를 저장함

인가(Authorization) 필요한 API 요청/응답

  1. 클라이언트는 쿠키(sessionId 담겨있음)와 함께 서버에 get 요청을 함
  2. 서버는 받은 sessionId를 세션 스토리지에서 validate 검사를 한 후
  3. 검사가 통과되면 서버는 DB에 클라이언트가 요청 데이터를 찾고
  4. 찾으면 DB는 관련 데이터를 다시 서버에 보냄
  5. 서버는 클라이언트에 응답할 때 클라이언트가 요청했던 데이터를 보냄

 
+) 참고

  • 세션 유지 상태 : 서버에서 관리하는 세션 저장소에 회원 데이터가 있음
  • 세션 만료 상태 : 서버에서 관리하는 세션 저장소에 회원 데이터가 없음

2-2. 토큰, JWT

토큰 : 클라이언트에서 보관하는 암호화된 인증 정보
(참고로 세션은 서버에서 sessionId를 발급해서 서버에서 보관하는 인증 정보이기 때문에
토큰은 세션처럼 서버에서 사용자의 인증 정보를 보관할 필요가 없기 때문에 서버 부담을 줄여주는 인증 수단)

웹에서 인증 수단으로 사용되는 토큰은 주로 JWT(Json Web Token) 기술로써 요즘 굉장히 많이 사용됨

그렇다면 여기서 JWT의 특징은 ?

  1. header / payload / signature 형식으로 3가지 데이터로 구성
  2. 국제 인터넷 표준 인증 규격 중 하나
  3. 암호화된 토큰을 누구나 복호화(해석)하여 정보(payload)를 볼 수 있음 → 누구나 해석가능하기 때문에 토큰의 용도는 인증정보(payload)에 대한 보호가 아니라 위조 방지 !
  4. 정보(payload)를 토큰화할 때 signature에 secret key가 필요하고, secret key는 복호화가 아니라 토큰이 유효한 지를 검증하는 데 사용

JWT 토큰 인증 방식 : JWT 토큰을 통해 클라이언트와 서버간의 통신(ex. 인증과 인가)하는 방식으로 CORS 옵션은 굉장히 중요

로그인/회원가입 시 세션 ‘인증’

  1. 클라이언트는 서버에 아이디와 패스워드를 보내서 로그인/회원가입 요청
  2. 서버는 요청받은 아이디와 패스워드를 DB에 보냄
  3. DB는 만약 해당 아이디와 패스워드가 존재한다면 서버에 그 유저에 데이터 객체를 보내줌(= 응답)
  4. 그럼 서버는 존재하는 유저인 경우 JWT 토큰을 Secret Key 의해서 발급함
  5. 서버는 응답시 (1)쿠키로써 JWT 토큰을 넣거나 (2)body에 JWT 토큰을 넣어서 클라이언트에게 응답함

‘인가(Authorization)’ 필요한 API 요청/응답

  1. 클라이언트는 헤더에서 갖고있는 토큰(ex. Bearer ${token})을 서버로 보내 get요청을 함 => 이때 리소스 접근 인가를 받기 위해 사용되는 토큰을 Access Token이라고 함
  2. 서버는 해당 토큰이 정말 유효한가 (우리가 발급했던 토큰이 맞는가) verify함
  3. 유효 검사가 끝나면 DB에 보내 데이터를 query 함
  4. DB는 해당 데이터를 다시 서버에 보냄(= 응답)
  5. 서버는 클라이언트에게 그 데이터를 보냄(= 응답)

 
+) Access Token가 브라우저의 쿠키에 들어있는데 이것은 탈취될 가능성이 있다는 것을 의미 => 그래서 Refresh Token 으로 보안 강화

  • Access Token의 만료기간을 짧게 잡음
  • 인증 보안이 중요한 서비스의 경우 인증 시 2개의 토큰 (Access Token, Refresh Token)을 발급하고 Access Token의 기간은 30분 정도로 짧게 가져 가고, Refresh Token은 1~2주 정도로 길게 잡는 식으로 지정해주면 됨
  • 이 경우 서버에서는 Access Token이 만료되었을 때 Refresh Token 이 유효한 상태면 새로운 Access Token을 클라이언트에 발급해주고 인증상태를 유지할 수 있도록 하고, Refresh Token 만료 시 다시 로그인하라는 메시지를 응답

 
+) 세션 인증 vs 토큰 인증


3. split 메서드

split() : 배열의 문자열을 지정한 구분자로 분해하여 배열을 반환하는 배열 메서드
split() mdn 문서

split() 기본형

split()
split(separator)
split(separator, limit)

예시

const str = 'The quick brown fox jumps over the lazy dog.';

const words = str.split(' '); // 구분자로 빈칸
console.log(words[3]); // "fox"

const chars = str.split(''); // 구분자로 빈 값
console.log(chars[8]); // "k"

const strCopy = str.split(); // 구분자가 아예 없는 경우
console.log(strCopy); // ["The quick brown fox jumps over the lazy dog."]

const strCopy = str.split(' ', 3); // 구분자로 빈칸과 배열의 개수 3개
console.log(); // ["The", "quick", "brown"]

0개의 댓글