Skip to content

Guest 로그인 중복 문제 해결

KWAKMANBO edited this page Jan 22, 2025 · 1 revision

⚠️ 문제 상황

Pasted image 20250122173351

  • 로컬 환경에서 Guest로그인을 사용하다 문제가 발생했다. 처음에는 잘되던 게스트로 로그인 버튼이 나중가서는 먹통이 되는 문제였다.
  • 게스트로 로그인 버튼을 클릭하면 서버에서는 임의의 유저를 생성해주는데 생성해주는 정보가 중복되서 생기는 문제 같았다.

🔬 문제 원인 분석

  • 게스트로 로그인 기능이 제대로 작동하지 않는 이유를 분석해보기로했다.

게스트 로그인 로직

Pasted image 20250123040353

  1. 게스트로 로그인버튼을 클릭하게되면 frontend 서버는 backend 서버의 /api/auth/tester/login로 요청을 보내게 된다.
  2. 요청이 들어오면 TestAuthGuard가 실행된다.
  3. TestAuthGuard는 TesterStrategy를 사용하며 해당 Statategy의 validate메소드를 사용함
  4. validate메소드에서는 testerAuthService의 attemptAuthentication메소드를 실행한다.
  5. attemptAuthentication메소드는 registerTester라는 메소드를 실행
  • 위와 같은 로직 순서로 실행되는데 이중 registerTester메소드만 확인하면 된다. 나머지는 registerTester라는 메소드를 실행하기위해 거쳐가는 중간 단계일뿐 다른 로직을 실행하는 부분은 없다.

registerTester 메소드

async registerTester() {  
  return this.register({  
    nickname: this.generateRandomNickname(),  
    email: 'tester@nav',  
    type: OauthType.LOCAL,  
    oauthId: String((await this.getMaxOauthId(OauthType.LOCAL)) + 1),  
  });  
}
  • nickname: this.generateRandomNickname() : this.generateRandomNickname() 랜돔으로 닉네임을 생성해주는 함수를 사용해 nickname을 생성하고, 생성한 nickname을 nickname에 저장
  • email: 'tester@nav' : 모든 게스트들은 email을 tester@nav로고정
  • type : Oauth의 타입을 지정하는 부분으로 google인지local인지를 의미한다.
  • oauthId : DB에 저장된 Oauth_ID 값중 가장 큰값을 가져와 +1을 해서 게스트 계정을 생성한다.
  • register 함수의 내용도 한번 알아보자

register메소드

async register({ nickname, email, type, oauthId }: RegisterRequest) {  
  return await this.dataSource.transaction(async (manager) => {  
    await this.validateUserExists(type, oauthId, manager);  
    const subName = await this.createSubName(nickname);  
    return await manager.save(User, {  
      nickname,  
      email,  
      type,  
      oauthId,  
      subName,  
    });  
  });  
}
  • DB에 유저를 생성하는 메소드 register의 내용이다.
  • await this.validateUserExists(type, oauthId, manager); 이 함수는 주어진 매개변수를 이용해 User테이블에 해당 값을 가진 user가 있는지 확인하고 없다면 유저를 생성하고 있다면 유저를 생성하지 못하게하는 함수이다.
  • 문제는 이 validateUserExists에서 발생한다. 이제부터 무슨 문제인지 알아보자

validateUserExists

  private async validateUserExists(
    type: OauthType,
    oauthId: string,
    manager: EntityManager,
  ) {
    if (await manager.exists(User, { where: { oauthId, type } })) {
      throw new BadRequestException('user already exists');
    }
  }
  • 문제 상황이였던 'user already exists'가 여기서 등장하는 것으로보아 oauthIdtype이 User 테이블 속에서 중복되서 발생하는 문제인것 같다. Pasted image 20250123041859
  • DB를 확인해보니 10개 까지는 계정이 잘만들어지는데 그 이후부터는 위처럼 에러가 발생하는 것을 알 수 있었다.
  • 그렇다면 OauthID는 어디서부터 오는것일까?찾아보니 registerTester() oauthId: String((await this.getMaxOauthId(OauthType.LOCAL)) + 1)부분에서 오는 것을 알 수 있었고 await this.getMaxOauthId(OauthType.LOCAL))함수도 확인해보기로 했다.

getMaxOauthId

private async getMaxOauthId(oauthType: OauthType) {  
   const result = await this.dataSource.manager  
  .createQueryBuilder(User, 'user')  
  .select('MAX(user.oauthId)', 'max')  
  .where('user.type = :oauthType', { oauthType })  
  .getRawOne();
  console.log(result.max);  
  return result ? Number(result.max) : 1;  
}
  • 위와 같은 쿼리를 이용해 user테이블에서 OauthId가 가장 큰값을 가져오는것을 알 수 있었다. Pasted image 20250123042315
  • 위와같은 에러가 뜨고, 분명히 우리는 OauthID가 10까지 있는것을 확인했는데 이 getMaxOauthId함수는 max값으로 10이아닌 9를 가져와서 생기는 문제임을 알 수 있었다.
  • 이제 문제가 일어나는 이유는 알았다. 그럼 문제의 근본적인 원인이 무엇인지 알아보보자

🔥 근본적인 문제원인

  • 문제의 근본적인 원인은 User 테이블의 Oauth_Id의 타입 문제였다.
  • users테이블의 DDL을 보며 근본적인 원인을 알아보자
-- auto-generated definition  
create table users  
(  
    id         int auto_increment  
        primary key,  
    nickname   varchar(50)                               not null,  
    subName    varchar(10)  default '0001'               not null,  
    email      varchar(50)                               not null,  
    role       varchar(5)   default 'USER'               not null,  
    type       varchar(10)  default 'local'              not null,  
    is_light   tinyint      default 1                    not null,  
    created_at timestamp(6) default CURRENT_TIMESTAMP(6) not null,  
    updated_at timestamp(6) default CURRENT_TIMESTAMP(6) not null on update CURRENT_TIMESTAMP(6),  
    oauth_id   varchar(255)                              not null,  
    constraint nickname_sub_name  
        unique (nickname, subName),  
    constraint type_oauth_id  
        unique (type, oauth_id)  
);
  • 위 users테이블의 DDL을 보면 oauth_id가 varchar(255)로 선언되어있는 것을 확인할 수 있는데 위 getMaxOauthId메소드는 숫자를 비교하는것을 염두에 뒀지만, 실제로는 문자열이 비교되서 생기는 문제였다.
  • 그렇기 때문에 게스트 유저가 10개가 되고나서부터는 중복 되는 문제가 생긴거였는데, 그 이유는 users테이블 oauth_id에서 숫자로치면 10이 제일 큰값이지만, 문자열로치면 '9' 가 '10'보다 크기 때문에 9를 max값으로 반환하기 때문이였다.
  • 정리하면 oauth_id가 문자열이기 떄문에 숫자비교가 아닌 문자열로 비교되서 생기는 문제였다.

🔨 문제를 해결해보자

  • 이제는 문제를 해결해보자
  • 문제를 해결하는 방식은 2가지가 있다.

1. oauth_id의 타입을 varchar 에서 int 로변경하기

  • users 테이블 DDL에서 oauth_id의 타입을 varchar 에서 int 로변경하는 방법이 있다.
  • 이방법은 근본적인 원인을 바로 해결할 수 있지만, 타입을 바꾸면 다른 메소드에도 영향이 갈 수 있어 또다른 수정이 생길 수 있다는 단점이 있다.

2. DB에서 불러올때 타입변환해서 비교하기

  • DB에서 oauth_id를 불러올떄 타입 변환을 해서 비교하는 방법이다
  • 이방법은 불러올때 oauth_id를 직접 타입 변환을 해야하기 떄문에 오버플로우가 발생한다. 하지만 해당 getMaxOauthId메소드 내에서만 변경되기 떄문에 1번의 또다른 수정 발생을 막을 수 있다는 장점이 있다.
  • 리팩토링 기간이 얼마 남지 않아 나는 2번 방법을 선택했다.

문제 해결 코드

private async getMaxOauthId(oauthType: OauthType) {  
  const result = await this.dataSource.manager  
    .createQueryBuilder(User, 'user')  
    .select('MAX(CAST(user.oauthId AS SIGNED))', 'max')  
    .where('user.type = :oauthType', { oauthType })  
    .getRawOne();  
  console.log(result.max);  
  return result ? Number(result.max) : 1;  
}

Pasted image 20250123043632 image

  • CAST(user.oauthId AS SIGNED)를 통해서 oauthId를 숫자로 변환해서 비교하는 방식으로 변경했다.
  • oauthId가 숫자로 변경되서 비교되기 때문에 정상적으로 users테이블의 oauth_id에서 제일 큰값을 가져오는 것을 아래 이미지에서 확인할 수 있다.
  • 로그인도 정상적으로 되는것을 확인할 수 있다.

팀 빌딩

📚팀 빌딩
📝Git 전략

회의록

1주차

🤝1월 7일
🤝1월 8일
🤝1월 9일

2주차

🤝주간 계획(1월 13일)

3주차

🤝주간 계획(1월 20일)

인공지능 리팩토링 1주차

🤝주간 계획(2월 3일)

인공지능 리팩토링 2주차

🤝주간 계획(2월 10일)

개발일지

AI 리팩토링 기획안

AI 리팩토링 개발일지

성능개선

리팩토링

팀회고

학습 정리

Clone this wiki locally