TIL - 2023/08/28

Hoony·2023년 8월 28일
0

Daily

목록 보기
25/30

💼 오늘 작업 내용

1. [RIGHT] 고객사 연장 프로세스 효율화

  • 종료일-7일 알림톡/이메일 나가는 거 확인 + 슬랙 알림 (NO 이탈자 + NO 연장자)

  • 종료일-4일 알림톡/이메일 나가는 거 확인 + 슬랙 알림 (설문지 미제출자)

  • 종료일-2일 연장 로그 (새로운 자동 로그 생성)

  • 종료 당일

    	- 연장 로그는 새로운 로그 개시되었다는 알림
    
    	- 이탈 로그는 기존 로그가 종료되었다는 알림
  • 서비스 종료 타입폼 제출 시 재피어 연동 확인

  • 종료일-7일 로그 찾아서 미리 조사해서 retention팀과 논의


zapier 설정

종료일 7일전 / 종료일 4일전 / 종료일 2일전 자동 연장 / 종료 당일



2. [RIGHT] 토큰 refresh 동시성 처리


동시성 이슈로 계속 유저가 로그아웃 되는 현상이 발생

맨 처음엔 promise / async&await 방식으로 처리를 하려 했으나, 해당 방식도 문제가 있음.

(거의 동시다발적으로 나갈 경우 해당 방식으로도 처리가 불가능)


그래서 기존 refresh 토큰 발급 구조를 변경하기로 함.

기존에는 refresh 일어날때마다 계속해서 refresh 토큰이 새로 발급되는 방식이라면,

이제는 새로운 토큰을 발급해주지 않고, 계속해서 해당 토큰을 유지하는 방식으로 진행됨.


해당 방식으로 진행하면, API 요청이 날아가는 도중에 refresh 토큰이 변경되는 경우가 발생하지 않아서 문제 해결 가능.



3. 코딩테스트 공부

  • programmers - 자동차 종류 별 특정 옵션이 포함된 자동차 수 구하기 - 151137
-- 코드를 입력하세요
select CAR_TYPE, count(*) as CARS
from car_rental_company_car
where (options LIKE '%통풍시트%') or (options LIKE '%열선시트%') or (options LIKE '%가죽시트%')
group by car_type
order by car_type asc

  • programmers - 오픈채팅방 - 42888
def solution(record):
    answer = []
    user_name_dict = {}
    admin_message_list = []
    for rec in record:
        data  = rec.split(" ")
        
        if(data[0] == 'Leave'):
            in_out, user_id  = data
        else:
            in_out, user_id, nickname = data
            
        
        # 이름 변경
        if(in_out == 'Enter' or in_out == 'Change'):
            user_name_dict[user_id] = nickname
        
        # 메시지 추가
        if(in_out == 'Change'): continue
        admin_message_list.append([user_id, 0 if in_out == 'Leave' else 1])
    
    for user_id, isEnter in admin_message_list:
        if(isEnter):
            answer.append(f'{user_name_dict[user_id]}님이 들어왔습니다.')
        else:
            answer.append(f'{user_name_dict[user_id]}님이 나갔습니다.')
    
    return answer


⚠️ 발생 에러

1. [RIGHT] 토큰 refresh 동시성 이슈

현재 회원 인증 시스템은 JWT 토큰 방식으로 access token & refresh token 을 이용하여 구현했다.

이때 refresh token 은 매 access token 이 만료되어 refresh 할때마다 access token과 함께 refresh token도 refresh를 진행했다.

이런 방식으로 진행하면, refresh token이 계속 새로 교체되어 더 높은 보안성을 유지할 수 있다.


프론트에서 백엔드로 특정 정보를 요구할 때, access token 을 확인하고 만료된 토큰이면,

refresh 함수가 동작하여 refresh token 을 확인하고 Redis에 저장되어 있는 기존 토큰을 삭제하고 새로운 refresh tokenRedis에 저장을 한다.


그러나 여기서 동시성 문제가 발생했다.

프론트에서 백엔드로 동시다발적으로 여러 API를 보낸다. 그리고 이로 인해 토큰을 확인하고 만료된 토큰이면 refresh가 일어나는 과정도 동시다발적으로 이루어진다.


그러나 refresh token은 가장 처음에 도착한 요청에서 이미 교체되어 새로운 refresh tokenRedis에 저장이 되어있다.


그럼 뒤이어 도착하는 요청들은 바뀌기 전인 기존 refresh token 을 가지고 인증을 시도하게 되고, 이로 인해 사용자가 올바른 refresh token을 가지고 있음에도 로그아웃이 될 수 있다.


이를 해결하기 위해선, queuepromise & async/await 방식을 이용해야 한다.

즉, 동시다발적으로 API 요청을 보내는 것이 아니라 앞선 요청이 끝나면 다음 요청이 나가는 식으로 순차적으로 진행이 되어야 한다.


가장 쉬운 방법은 promise를 이용하여 다른 API 요청이 나가있으면 해당 요청의 응답이 올때까지 wait 시키는 방법이다.


const accessToken =
		window.sessionStorage.getItem("accessToken") ||
		window.localStorage.getItem("accessToken") ||
		"";
	if (isTokenExpired(accessToken)) {
		if (refreshPromise) {
			await refreshPromise;
		}
		refreshPromise = new Promise((resolve) => {
			promiseResolve = resolve;
		});
		await getNewToken();
		if (promiseResolve) {
			promiseResolve(true);
		}
		refreshPromise = null;
		promiseResolve = null;
	}

이런 식으로 refreshPromise이 있을 경우 (이미 처리중인 요청이 있을 경우)

await 키워드를 통해 해당 요청이 끝날때까지 기다린 다음에 요청이 나가도록 설정할 수 있다.

profile
Just Do it!

0개의 댓글