소스 코드

[리포지 링크]


들어가기전에…

지난 포스팅에서 구글 OAuth로 유저 정보를 잘 받아오는지 확인했으니, 이번에는 책에서 가이드하는대로 UserEntity를 수정하고, 구글 로그인 시 회원 정보를 저장하는 로직도 추가하려 한다. 그리고 세션을 활용해 인증 시에 확인하는 기능도 추가해보려 한다


1. User 엔티티 파일 수정하기

password에 빈 값을 허용 설정하고, 구글 인증 시의 식별자인 providerId를 추가해보자

import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id?: number;

  @Column({ unique: true })
  email: string;

  // @Column() // 기존 코드 수정
  @Column({ nullable: true }) // 1. 패스워드에 빈 값 허용
  password: string;

  @Column()
  username: string;

  @Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' })
  createdDt: Date;

  @Column({ nullable: true }) // 2. providerId에 빈 값 허용
  providerId: string; // 3. provicerId 추가
}

1번. {nullable: true}로 설정하면 빈 값을 허용한다는 뜻

2번. providerId는 구글 OAuth로 가입하지 않은 경우에는 모르는 값이므로 빈 값을 허용

3번. providerId는 OAuth 인증 시 식별자로 사용할 수 있는 값이다


2. UserService에 구글 유저 검색 및 저장 메서드 추가하기

구글 OAuth 인증으로 받아온 이메일로 기존 가입 여부를 확인해 가입되어 있으며 유저 정보를 반환하고, 아니면 회원 정보를 유저 테이블에 저장하는 코드를 작성하자

... 생략 ...

@Injectable()
export class UserService {
  ... 생략 ...

  async findByEmailOrSave(email, username, providerId): Promise<User> {
    const foundUser = await this.getUser(email); // 1. 이메일로 유저를 찾음
    if (foundUser) { // 2. 찾았으면
      return foundUser; // 3. 유저 정보 반환
    }

    const newUser = await this.userRepository.save({ // 4. 유저 정보 없으면 저장
      email,
      username,
      providerId,
    });
    return newUser; // 5. 저장 후 유저 정보 반환
  }
}

1번. 이 클래스의 getUser(email) 메서드로 이메일로 가입 정보가 있는지 확인

2~3번. 유저 정보가 있다면, 유저 정보를 반환. 이때 반환값을 타입이 Promise이기에 호출하는 곳에서 await을 해야 올바르게 받을 수 있음

4번. 유저 정보가 없으면 email, username(성, 이름), providerId를 저장

5번. 유저 정보 저장 후 바로 저장된 유저 정보를 반환


3. GoogleStrategy에 구글 유저 저장하는 메서드 적용

google.strategy.ts의 validate() 메서드에서 구글 유저 정보가 있다면 정보를 데이터베이스에서 가져오고 없다면 저장해야 하므로 findByEmailOrSave() 메서드를 GoogleStrategy에 적용한다. validate() 메서드에서 profile 정보의 id, name, email을 디비에 저장하도록 User 엔티티에 맞춰서 넘겨준다

// src > auth > google.strategy.ts

... 생략 ...
@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy) {

    ... 생략 ...
    async validate(accessToken: string, refreshToken: string, profile: Profile) {

        ... 생략 ...
        // 1. 유저 정보 저장 혹은 가져오기
        const user: User = await this.userService.findByEmailOrSave(
          email,
          name.familyName + name.givenName,
          providerId,
        );

        // 2. 유저 정보 반환
        return user;
        // console.log(providerId, email, name.familyName, name.givenName);
        // return profile;
    }
}

1번. userService에 findByEmailOrSave() 메서드를 추가해 유저 정보를 가져온다

2번. 수정 전에는 OAuth에 있는 profile을 반환했으나 이제는 DB에 저장한 user 정보를 반환한다. 세션에서는 유저 정보를 다룰 때 userEntity를 사용한다. OAuth 정보를 담은 User 엔티티를 사용하도록 수정했으므로 이제 세션을 사용할 수 있다. OAuth 정보를 사용하는 세션을 사용하도록 GoogleAuthGuard를 수정한다


4. GoogleAuthGuard에 세션을 사용하도록 변경

로그인 시에만 구글 OAuth 요청을 하고 그 뒤로는 세션에 저장된 데이터로 인증을 확인하도록 코드를 변경한다

// src > auth > auth.guard.ts

... 생략 ...
@Injectable()
export class GoogleAuthGuard extends AuthGuard('google') {
  async canActivate(context: any): Promise<boolean> {
    const result = (await super.canActivate(context)) as boolean;
    const request = context.switchToHttp().getRequest();
    await super.logIn(request); // 1. 세션 적용
    return result;
  }

1번. 부모 클래스의 logIn(request)를 실행한다. 세션 기능이 활성화되어 있다면 SessionSerializer를 실행해 request.user의 값을 세션에 저장한다. 이제 데이터베이스에 유저 데이터가 저장되어 있고 세션도 사용할 수 있다


5. 테스트

http:localhost:3000/auth/to-google로 로그인한 후, auth-test.sqlite 파일을 확인하면, providerId값이 들어와 있다.

image-20230904152807966

image-20230904152909115


정리

  • 액세스 토큰은 리소스 서버에서 리소스 소유자의 보호된 정보를 획득할 때 사용하는 만료 기간이 있는 토큰
  • 리프레시 토큰을 사용해 인가 서버에서 새로운 액세스 토큰을 발급받아야 한다. 선택적으로 리프레시 토큰도 재발급받을 수 있다.

참고

다음에 다루게 될 것

  • 파일 업로드 기능 구현하기

댓글남기기